diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02c085d0b5..b6f25f1858 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+Changes in [2.3.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.1) (2020-04-01)
+===================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0...v2.3.1)
+
+ * Fix jitsi popout URL
+ [\#4327](https://github.com/matrix-org/matrix-react-sdk/pull/4327)
+ * Remove underscore from Jitsi conference names
+ [\#4324](https://github.com/matrix-org/matrix-react-sdk/pull/4324)
+ * Fix popout support for jitsi widgets
+ [\#4322](https://github.com/matrix-org/matrix-react-sdk/pull/4322)
+
Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0)
diff --git a/docs/settings.md b/docs/settings.md
index 9b780c27c9..46e4a68fdb 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -51,6 +51,17 @@ Settings are the different options a user may set or experience in the applicati
}
```
+Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some settings, like the "theme" setting, are special cased in the config file):
+```json
+{
+ ...
+ "settingDefaults": {
+ "settingName": true
+ },
+ ...
+}
+```
+
### Getting values for a setting
After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always
diff --git a/package.json b/package.json
index 7001b1cb21..c9300a13ca 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "2.3.0",
+ "version": "2.3.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss
index 85007aeecb..05cddf2c48 100644
--- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss
+++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss
@@ -60,3 +60,14 @@ limitations under the License.
.mx_InteractiveAuthEntryComponents_passwordSection {
width: 300px;
}
+
+.mx_InteractiveAuthEntryComponents_sso_buttons {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ margin-top: 20px;
+
+ .mx_AccessibleButton {
+ margin-left: 5px;
+ }
+}
diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss
index b87071745d..de39525588 100644
--- a/res/css/views/elements/_AccessibleButton.scss
+++ b/res/css/views/elements/_AccessibleButton.scss
@@ -36,12 +36,20 @@ limitations under the License.
font-weight: 600;
}
+.mx_AccessibleButton_kind_primary_outline {
+ color: $button-primary-bg-color;
+ background-color: $button-secondary-bg-color;
+ border: 1px solid $button-primary-bg-color;
+ font-weight: 600;
+}
+
.mx_AccessibleButton_kind_secondary {
color: $accent-color;
font-weight: 600;
}
-.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {
+.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled,
+.mx_AccessibleButton_kind_primary_outline.mx_AccessibleButton_disabled {
opacity: 0.4;
}
@@ -60,7 +68,14 @@ limitations under the License.
background-color: $button-danger-bg-color;
}
-.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
+.mx_AccessibleButton_kind_danger_outline {
+ color: $button-danger-bg-color;
+ background-color: $button-secondary-bg-color;
+ border: 1px solid $button-danger-bg-color;
+}
+
+.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled,
+.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color;
}
diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss
index 0e4b1bda9e..d0e13aa06b 100644
--- a/res/css/views/right_panel/_UserInfo.scss
+++ b/res/css/views/right_panel/_UserInfo.scss
@@ -266,12 +266,31 @@ limitations under the License.
}
}
+ .mx_AccessibleButton {
+ padding: 8px 18px;
+
+ &.mx_AccessibleButton_kind_primary {
+ color: $accent-color;
+ background-color: $accent-bg-color;
+ }
+
+ &.mx_AccessibleButton_kind_danger {
+ color: $notice-primary-color;
+ background-color: $notice-primary-bg-color;
+ }
+ }
+
+ .mx_VerificationShowSas .mx_AccessibleButton,
.mx_UserInfo_wideButton {
display: block;
- margin: 16px 0;
+ margin: 16px 0 8px;
}
- button.mx_UserInfo_wideButton {
- width: 100%; // FIXME get rid of this once we get rid of DialogButtons here
+
+
+ .mx_VerificationShowSas {
+ .mx_AccessibleButton + .mx_AccessibleButton {
+ margin: 8px 0; // space between buttons
+ }
}
}
diff --git a/src/AddThreepid.js b/src/AddThreepid.js
index 7a3250d0ca..f06f7c187d 100644
--- a/src/AddThreepid.js
+++ b/src/AddThreepid.js
@@ -21,6 +21,7 @@ import * as sdk from './index';
import Modal from './Modal';
import { _t } from './languageHandler';
import IdentityAuthClient from './IdentityAuthClient';
+import {SSOAuthEntry} from "./components/views/auth/InteractiveAuthEntryComponents";
function getIdServerDomain() {
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
@@ -188,11 +189,31 @@ export default class AddThreepid {
// pop up an interactive auth dialog
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
+
+ const dialogAesthetics = {
+ [SSOAuthEntry.PHASE_PREAUTH]: {
+ title: _t("Use Single Sign On to continue"),
+ body: _t("Confirm adding this email address by using " +
+ "Single Sign On to prove your identity."),
+ continueText: _t("Single Sign On"),
+ continueKind: "primary",
+ },
+ [SSOAuthEntry.PHASE_POSTAUTH]: {
+ title: _t("Confirm adding email"),
+ body: _t("Click the button below to confirm adding this email address."),
+ continueText: _t("Confirm"),
+ continueKind: "primary",
+ },
+ };
const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, {
title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(),
authData: e.data,
makeRequest: this._makeAddThreepidOnlyRequest,
+ aestheticsForStagePhases: {
+ [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
+ [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+ },
});
return finished;
}
@@ -285,11 +306,30 @@ export default class AddThreepid {
// pop up an interactive auth dialog
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
+ const dialogAesthetics = {
+ [SSOAuthEntry.PHASE_PREAUTH]: {
+ title: _t("Use Single Sign On to continue"),
+ body: _t("Confirm adding this phone number by using " +
+ "Single Sign On to prove your identity."),
+ continueText: _t("Single Sign On"),
+ continueKind: "primary",
+ },
+ [SSOAuthEntry.PHASE_POSTAUTH]: {
+ title: _t("Confirm adding phone number"),
+ body: _t("Click the button below to confirm adding this phone number."),
+ continueText: _t("Confirm"),
+ continueKind: "primary",
+ },
+ };
const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, {
title: _t("Add Phone Number"),
matrixClient: MatrixClientPeg.get(),
authData: e.data,
makeRequest: this._makeAddThreepidOnlyRequest,
+ aestheticsForStagePhases: {
+ [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
+ [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+ },
});
return finished;
}
diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js
index b7b81688e1..05054cf63a 100644
--- a/src/AsyncWrapper.js
+++ b/src/AsyncWrapper.js
@@ -38,7 +38,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js
index ea76c85643..3c4494c09d 100644
--- a/src/FromWidgetPostMessageApi.js
+++ b/src/FromWidgetPostMessageApi.js
@@ -24,8 +24,7 @@ import {MatrixClientPeg} from "./MatrixClientPeg";
import RoomViewStore from "./stores/RoomViewStore";
import {IntegrationManagers} from "./integrations/IntegrationManagers";
import SettingsStore from "./settings/SettingsStore";
-import {Capability, KnownWidgetActions} from "./widgets/WidgetApi";
-import SdkConfig from "./SdkConfig";
+import {Capability} from "./widgets/WidgetApi";
const WIDGET_API_VERSION = '0.0.2'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [
@@ -220,13 +219,6 @@ export default class FromWidgetPostMessageApi {
}
} else if (action === 'get_openid') {
// Handled by caller
- } else if (action === KnownWidgetActions.GetRiotWebConfig) {
- if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.GetRiotWebConfig)) {
- this.sendResponse(event, {
- api: INBOUND_API_NAME,
- config: SdkConfig.get(),
- });
- }
} else {
console.warn('Widget postMessage event unhandled');
this.sendError(event, {message: 'The postMessage was unhandled'});
diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts
index 34f3402334..400d29a20f 100644
--- a/src/SdkConfig.ts
+++ b/src/SdkConfig.ts
@@ -30,8 +30,6 @@ export const DEFAULTS: ConfigOptions = {
jitsi: {
// Default conference domain
preferredDomain: "jitsi.riot.im",
- // Default Jitsi Meet API location
- externalApiUrl: "https://jitsi.riot.im/libs/external_api.min.js",
},
};
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index c62aa9c3e5..d60434cf97 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -412,17 +412,20 @@ export const Commands = [
button: _t("Continue"),
},
));
+
+ finished = finished.then(([useDefault]: any) => {
+ if (useDefault) {
+ useDefaultIdentityServer();
+ return;
+ }
+ throw new Error(_t("Use an identity server to invite by email. Manage in Settings."));
+ });
} else {
return reject(_t("Use an identity server to invite by email. Manage in Settings."));
}
}
const inviter = new MultiInviter(roomId);
- return success(finished.then(([useDefault]: any) => {
- if (useDefault) {
- useDefaultIdentityServer();
- } else if (useDefault === false) {
- throw new Error(_t("Use an identity server to invite by email. Manage in Settings."));
- }
+ return success(finished.then(() => {
return inviter.invite([address]);
}).then(() => {
if (inviter.getCompletionState(address) !== "invited") {
diff --git a/src/async-components/views/dialogs/EncryptedEventDialog.js b/src/async-components/views/dialogs/EncryptedEventDialog.js
index b602cf60fe..9eb4439816 100644
--- a/src/async-components/views/dialogs/EncryptedEventDialog.js
+++ b/src/async-components/views/dialogs/EncryptedEventDialog.js
@@ -37,7 +37,7 @@ export default createReactClass({
return { device: null };
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
const client = MatrixClientPeg.get();
@@ -79,7 +79,7 @@ export default createReactClass({
},
onDeviceVerificationChanged: function(userId, device) {
- if (userId == this.props.event.getSender()) {
+ if (userId === this.props.event.getSender()) {
this.refreshDevice().then((dev) => {
this.setState({ device: dev });
});
diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js
index 3d7249b5a1..5f24fb10fa 100644
--- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js
+++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js
@@ -30,7 +30,7 @@ import EventIndexPeg from "../../../../indexing/EventIndexPeg";
export default class ManageEventIndexDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
- }
+ };
constructor(props) {
super(props);
@@ -82,7 +82,7 @@ export default class ManageEventIndexDialog extends React.Component {
}
}
- async componentWillMount(): void {
+ async componentDidMount(): void {
let eventIndexSize = 0;
let crawlingRoomsCount = 0;
let roomCount = 0;
@@ -126,16 +126,12 @@ export default class ManageEventIndexDialog extends React.Component {
import("./DisableEventIndexDialog"),
null, null, /* priority = */ false, /* static = */ true,
);
- }
-
- _onDone = () => {
- this.props.onFinished(true);
- }
+ };
_onCrawlerSleepTimeChange = (e) => {
this.setState({crawlerSleepTime: e.target.value});
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
- }
+ };
render() {
let crawlerState;
diff --git a/src/components/structures/CustomRoomTagPanel.js b/src/components/structures/CustomRoomTagPanel.js
index e8ff6e814e..6e392ea505 100644
--- a/src/components/structures/CustomRoomTagPanel.js
+++ b/src/components/structures/CustomRoomTagPanel.js
@@ -30,7 +30,7 @@ class CustomRoomTagPanel extends React.Component {
};
}
- componentWillMount() {
+ componentDidMount() {
this._tagStoreToken = CustomRoomTagStore.addListener(() => {
this.setState({tags: CustomRoomTagStore.getSortedTags()});
});
diff --git a/src/components/structures/EmbeddedPage.js b/src/components/structures/EmbeddedPage.js
index a0a95ac6f1..0aababf030 100644
--- a/src/components/structures/EmbeddedPage.js
+++ b/src/components/structures/EmbeddedPage.js
@@ -58,7 +58,7 @@ export default class EmbeddedPage extends React.PureComponent {
return sanitizeHtml(_t(s));
}
- componentWillMount() {
+ componentDidMount() {
this._unmounted = false;
if (!this.props.url) {
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 524694fe95..73a8879f6d 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -428,12 +428,11 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
this._matrixClient = MatrixClientPeg.get();
this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
- this._changeAvatarComponent = null;
this._initGroupStore(this.props.groupId, true);
this._dispatcherRef = dis.register(this._onAction);
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
index b2441f1bf8..351e3bbad0 100644
--- a/src/components/structures/InteractiveAuth.js
+++ b/src/components/structures/InteractiveAuth.js
@@ -1,6 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd.
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 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.
@@ -24,6 +24,8 @@ import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryCom
import * as sdk from '../../index';
+export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
+
export default createReactClass({
displayName: 'InteractiveAuth',
@@ -47,7 +49,7 @@ export default createReactClass({
// @param {bool} status True if the operation requiring
// auth was completed sucessfully, false if canceled.
// @param {object} result The result of the authenticated call
- // if successful, otherwise the error object
+ // if successful, otherwise the error object.
// @param {object} extra Additional information about the UI Auth
// process:
// * emailSid {string} If email auth was performed, the sid of
@@ -75,6 +77,15 @@ export default createReactClass({
// is managed by some other party and should not be managed by
// the component itself.
continueIsManaged: PropTypes.bool,
+
+ // Called when the stage changes, or the stage's phase changes. First
+ // argument is the stage, second is the phase. Some stages do not have
+ // phases and will be counted as 0 (numeric).
+ onStagePhaseChange: PropTypes.func,
+
+ // continueText and continueKind are passed straight through to the AuthEntryComponent.
+ continueText: PropTypes.string,
+ continueKind: PropTypes.string,
},
getInitialState: function() {
@@ -205,6 +216,16 @@ export default createReactClass({
this._authLogic.submitAuthDict(authData);
},
+ _onPhaseChange: function(newPhase) {
+ if (this.props.onStagePhaseChange) {
+ this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
+ }
+ },
+
+ _onStageCancel: function() {
+ this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
+ },
+
_renderCurrentStage: function() {
const stage = this.state.authStage;
if (!stage) {
@@ -233,6 +254,10 @@ export default createReactClass({
fail={this._onAuthStageFailed}
setEmailSid={this._setEmailSid}
showContinue={!this.props.continueIsManaged}
+ onPhaseChange={this._onPhaseChange}
+ continueText={this.props.continueText}
+ continueKind={this.props.continueKind}
+ onCancel={this._onStageCancel}
/>
);
},
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index f5e0bca67e..bbeaeb10e0 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -44,7 +44,7 @@ const LeftPanel = createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this.focusedElement = null;
this._breadcrumbsWatcherRef = SettingsStore.watchSetting(
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 02ca8df0a7..51ce41e36f 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -22,7 +22,7 @@ import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
-import { Key, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
+import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard';
import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import { fixupColorFonts } from '../../utils/FontManager';
@@ -381,7 +381,7 @@ const LoggedInView = createReactClass({
break;
case Key.SLASH:
- if (ctrlCmdOnly) {
+ if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev)) {
KeyboardShortcuts.toggleDialog();
handled = true;
}
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 258972d18d..4ef0f60e4b 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -221,7 +221,8 @@ export default createReactClass({
return {serverConfig: props};
},
- componentWillMount: function() {
+ // TODO: [REACT-WARNING] Move this to constructor
+ UNSAFE_componentWillMount: function() {
SdkConfig.put(this.props.config);
// Used by _viewRoom before getting state from sync
@@ -261,9 +262,7 @@ export default createReactClass({
this._accountPassword = null;
this._accountPasswordTimer = null;
- },
- componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this._themeWatcher = new ThemeWatcher();
this._themeWatcher.start();
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index f1209b7b9e..f179cab6ad 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -38,7 +38,7 @@ export default createReactClass({
contextType: MatrixClientContext,
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._fetch();
},
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 8d25116827..5e556419cc 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -108,7 +108,7 @@ export default class RightPanel extends React.Component {
}
}
- componentWillMount() {
+ componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
const cli = this.context;
cli.on("RoomState.members", this.onRoomStateMember);
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 664aaaf21f..0b07c10c8a 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -56,7 +56,8 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ // TODO: [REACT-WARNING] Move this to constructor
+ UNSAFE_componentWillMount: function() {
this._unmounted = false;
this.nextBatch = null;
this.filterTimeout = null;
@@ -89,9 +90,7 @@ export default createReactClass({
),
});
});
- },
- componentDidMount: function() {
this.refreshRoomList();
},
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 13b73ec02b..639f38a119 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -96,7 +96,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 9428de3e22..2ae2d71100 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -126,7 +126,7 @@ export default class RoomSubList extends React.PureComponent {
break;
case 'view_room':
- if (this.state.hidden && !this.props.forceExpand &&
+ if (this.state.hidden && !this.props.forceExpand && payload.show_room_tile &&
this.props.list.some((r) => r.roomId === payload.room_id)
) {
this.toggle();
@@ -193,6 +193,7 @@ export default class RoomSubList extends React.PureComponent {
onRoomTileClick = (roomId, ev) => {
dis.dispatch({
action: 'view_room',
+ show_room_tile: true, // to make sure the room gets scrolled into view
room_id: roomId,
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
});
diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js
index f1a39d6fcf..6642cce098 100644
--- a/src/components/structures/TagPanel.js
+++ b/src/components/structures/TagPanel.js
@@ -44,7 +44,7 @@ const TagPanel = createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this.unmounted = false;
this.context.on("Group.myMembership", this._onGroupMyMembership);
this.context.on("sync", this._onClientSync);
diff --git a/src/components/structures/UserView.js b/src/components/structures/UserView.js
index 94159a1da4..c4fba137cc 100644
--- a/src/components/structures/UserView.js
+++ b/src/components/structures/UserView.js
@@ -35,7 +35,7 @@ export default class UserView extends React.Component {
this.state = {};
}
- componentWillMount() {
+ componentDidMount() {
if (this.props.userId) {
this._loadProfileInfo();
}
diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js
index c849edf260..dc3f4a0a2b 100644
--- a/src/components/structures/auth/ForgotPassword.js
+++ b/src/components/structures/auth/ForgotPassword.js
@@ -69,7 +69,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this.reset = null;
this._checkServerLiveliness(this.props.serverConfig);
},
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index bfabc34a62..ef9d4c59c0 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -113,7 +113,8 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ // TODO: [REACT-WARNING] Move this to constructor
+ UNSAFE_componentWillMount: function() {
this._unmounted = false;
// map from login step type to a function which will render a control
diff --git a/src/components/structures/auth/PostRegistration.js b/src/components/structures/auth/PostRegistration.js
index 8eef8dce11..687ab9a195 100644
--- a/src/components/structures/auth/PostRegistration.js
+++ b/src/components/structures/auth/PostRegistration.js
@@ -37,7 +37,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
// There is some assymetry between ChangeDisplayName and ChangeAvatar,
// as ChangeDisplayName will auto-get the name but ChangeAvatar expects
// the URL to be passed to you (because it's also used for room avatars).
diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js
index 7c6a3ea56f..45d775059c 100644
--- a/src/components/structures/auth/Registration.js
+++ b/src/components/structures/auth/Registration.js
@@ -120,7 +120,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
this._replaceClient();
},
diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js
index 287f7e5605..08ab7e8a61 100644
--- a/src/components/structures/auth/SoftLogout.js
+++ b/src/components/structures/auth/SoftLogout.js
@@ -54,7 +54,7 @@ export default class SoftLogout extends React.Component {
this.state = {
loginView: LOGIN_VIEW.LOADING,
- keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount)
+ keyBackupNeeded: true, // assume we do while we figure it out (see componentDidMount)
busy: false,
password: "",
diff --git a/src/components/views/auth/CountryDropdown.js b/src/components/views/auth/CountryDropdown.js
index 63dc9d1ada..14a974668b 100644
--- a/src/components/views/auth/CountryDropdown.js
+++ b/src/components/views/auth/CountryDropdown.js
@@ -60,7 +60,7 @@ export default class CountryDropdown extends React.Component {
};
}
- componentWillMount() {
+ componentDidMount() {
if (!this.props.value) {
// If no value is given, we start with the default
// country selected, but our parent component
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js
index 794f270cf3..0ed93ed7d3 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.js
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.js
@@ -1,7 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 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.
@@ -25,6 +25,7 @@ import classnames from 'classnames';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
+import AccessibleButton from "../elements/AccessibleButton";
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@@ -59,11 +60,21 @@ import SettingsStore from "../../../settings/SettingsStore";
* session to be failed and the process to go back to the start.
* setEmailSid: m.login.email.identity only: a function to be called with the
* email sid after a token is requested.
+ * onPhaseChange: A function which is called when the stage's phase changes. If
+ * the stage has no phases, call this with DEFAULT_PHASE. Takes
+ * one argument, the phase, and is always defined/required.
+ * continueText: For stages which have a continue button, the text to use.
+ * continueKind: For stages which have a continue button, the style of button to
+ * use. For example, 'danger' or 'primary'.
+ * onCancel A function with no arguments which is called by the stage if the
+ * user knowingly cancelled/dismissed the authentication attempt.
*
* Each component may also provide the following functions (beyond the standard React ones):
* focus: set the input focus appropriately in the form.
*/
+export const DEFAULT_PHASE = 0;
+
export const PasswordAuthEntry = createReactClass({
displayName: 'PasswordAuthEntry',
@@ -78,6 +89,11 @@ export const PasswordAuthEntry = createReactClass({
// is the auth logic currently waiting for something to
// happen?
busy: PropTypes.bool,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
getInitialState: function() {
@@ -175,6 +191,11 @@ export const RecaptchaAuthEntry = createReactClass({
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
_onCaptchaResponse: function(response) {
@@ -236,6 +257,11 @@ export const TermsAuthEntry = createReactClass({
errorText: PropTypes.string,
busy: PropTypes.bool,
showContinue: PropTypes.bool,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
// TODO: [REACT-WARNING] Move this to constructor
@@ -379,6 +405,11 @@ export const EmailIdentityAuthEntry = createReactClass({
stageState: PropTypes.object.isRequired,
fail: PropTypes.func.isRequired,
setEmailSid: PropTypes.func.isRequired,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
getInitialState: function() {
@@ -421,6 +452,11 @@ export const MsisdnAuthEntry = createReactClass({
clientSecret: PropTypes.func,
submitAuthDict: PropTypes.func.isRequired,
matrixClient: PropTypes.object,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
getInitialState: function() {
@@ -430,7 +466,7 @@ export const MsisdnAuthEntry = createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._submitUrl = null;
this._sid = null;
this._msisdn = null;
@@ -565,6 +601,91 @@ export const MsisdnAuthEntry = createReactClass({
},
});
+export class SSOAuthEntry extends React.Component {
+ static propTypes = {
+ matrixClient: PropTypes.object.isRequired,
+ authSessionId: PropTypes.string.isRequired,
+ loginType: PropTypes.string.isRequired,
+ submitAuthDict: PropTypes.func.isRequired,
+ errorText: PropTypes.string,
+ onPhaseChange: PropTypes.func.isRequired,
+ continueText: PropTypes.string,
+ continueKind: PropTypes.string,
+ onCancel: PropTypes.func,
+ };
+
+ static LOGIN_TYPE = "m.login.sso";
+ static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso";
+
+ static PHASE_PREAUTH = 1; // button to start SSO
+ static PHASE_POSTAUTH = 2; // button to confirm SSO completed
+
+ _ssoUrl: string;
+
+ constructor(props) {
+ super(props);
+
+ // We actually send the user through fallback auth so we don't have to
+ // deal with a redirect back to us, losing application context.
+ this._ssoUrl = props.matrixClient.getFallbackAuthUrl(
+ this.props.loginType,
+ this.props.authSessionId,
+ );
+
+ this.state = {
+ phase: SSOAuthEntry.PHASE_PREAUTH,
+ };
+ }
+
+ componentDidMount(): void {
+ this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH);
+ }
+
+ onStartAuthClick = () => {
+ // Note: We don't use PlatformPeg's startSsoAuth functions because we almost
+ // certainly will need to open the thing in a new tab to avoid losing application
+ // context.
+
+ window.open(this._ssoUrl, '_blank');
+ this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH});
+ this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
+ };
+
+ onConfirmClick = () => {
+ this.props.submitAuthDict({});
+ };
+
+ render() {
+ let continueButton = null;
+ const cancelButton = (
+ {_t("Cancel")}
+ );
+ if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) {
+ continueButton = (
+ {this.props.continueText || _t("Single Sign On")}
+ );
+ } else {
+ continueButton = (
+ {this.props.continueText || _t("Confirm")}
+ );
+ }
+
+ return
+ {cancelButton}
+ {continueButton}
+
;
+ }
+}
+
export const FallbackAuthEntry = createReactClass({
displayName: 'FallbackAuthEntry',
@@ -574,6 +695,11 @@ export const FallbackAuthEntry = createReactClass({
loginType: PropTypes.string.isRequired,
submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string,
+ onPhaseChange: PropTypes.func.isRequired,
+ },
+
+ componentDidMount: function() {
+ this.props.onPhaseChange(DEFAULT_PHASE);
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
@@ -599,7 +725,10 @@ export const FallbackAuthEntry = createReactClass({
}
},
- _onShowFallbackClick: function() {
+ _onShowFallbackClick: function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
const url = this.props.matrixClient.getFallbackAuthUrl(
this.props.loginType,
this.props.authSessionId,
@@ -628,7 +757,7 @@ export const FallbackAuthEntry = createReactClass({
}
return (
);
@@ -641,11 +770,12 @@ const AuthEntryComponents = [
EmailIdentityAuthEntry,
MsisdnAuthEntry,
TermsAuthEntry,
+ SSOAuthEntry,
];
export default function getEntryComponentForLoginType(loginType) {
for (const c of AuthEntryComponents) {
- if (c.LOGIN_TYPE == loginType) {
+ if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) {
return c;
}
}
diff --git a/src/components/views/avatars/MemberStatusMessageAvatar.js b/src/components/views/avatars/MemberStatusMessageAvatar.js
index 54f11e8e91..eef3f86d9a 100644
--- a/src/components/views/avatars/MemberStatusMessageAvatar.js
+++ b/src/components/views/avatars/MemberStatusMessageAvatar.js
@@ -49,7 +49,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
this._button = createRef();
}
- componentWillMount() {
+ componentDidMount() {
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
}
diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js
index 4fc6dd58cc..452489aa65 100644
--- a/src/components/views/context_menus/MessageContextMenu.js
+++ b/src/components/views/context_menus/MessageContextMenu.js
@@ -61,7 +61,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions();
},
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 2d8dec29c7..d281656bbe 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -82,7 +82,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
},
diff --git a/src/components/views/context_menus/StatusMessageContextMenu.js b/src/components/views/context_menus/StatusMessageContextMenu.js
index d5cba45956..5e6f06dd5d 100644
--- a/src/components/views/context_menus/StatusMessageContextMenu.js
+++ b/src/components/views/context_menus/StatusMessageContextMenu.js
@@ -35,7 +35,7 @@ export default class StatusMessageContextMenu extends React.Component {
};
}
- componentWillMount() {
+ componentDidMount() {
const { user } = this.props;
if (!user) {
return;
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js
index 14910fbf6d..2495c46327 100644
--- a/src/components/views/dialogs/ConfirmUserActionDialog.js
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.js
@@ -55,7 +55,8 @@ export default createReactClass({
askReason: false,
}),
- componentWillMount: function() {
+ // TODO: [REACT-WARNING] Move this to constructor
+ UNSAFE_componentWillMount: function() {
this._reasonField = null;
},
diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js
index f7826b9c27..39e391269c 100644
--- a/src/components/views/dialogs/DeviceVerifyDialog.js
+++ b/src/components/views/dialogs/DeviceVerifyDialog.js
@@ -279,6 +279,7 @@ export default class DeviceVerifyDialog extends React.Component {
onDone={this._onSasMatchesClick}
isSelf={MatrixClientPeg.get().getUserId() === this.props.userId}
onStartEmoji={this._onUseSasClick}
+ inDialog={true}
/>;
}
diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js
index c612043919..2a4ff9cec3 100644
--- a/src/components/views/dialogs/IncomingSasDialog.js
+++ b/src/components/views/dialogs/IncomingSasDialog.js
@@ -196,7 +196,8 @@ export default class IncomingSasDialog extends React.Component {
sas={this._showSasEvent.sas}
onCancel={this._onCancelClick}
onDone={this._onSasMatchesClick}
- isSelf={this.props.verifier.userId == MatrixClientPeg.get().getUserId()}
+ isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()}
+ inDialog={true}
/>;
}
diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js
index ff9f55cb74..af5dc5108c 100644
--- a/src/components/views/dialogs/InteractiveAuthDialog.js
+++ b/src/components/views/dialogs/InteractiveAuthDialog.js
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
+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.
@@ -23,6 +24,7 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
+import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
export default createReactClass({
displayName: 'InteractiveAuthDialog',
@@ -44,12 +46,36 @@ export default createReactClass({
onFinished: PropTypes.func.isRequired,
+ // Optional title and body to show when not showing a particular stage
title: PropTypes.string,
+ body: PropTypes.string,
+
+ // Optional title and body pairs for particular stages and phases within
+ // those stages. Object structure/example is:
+ // {
+ // "org.example.stage_type": {
+ // 1: {
+ // "body": "This is a body for phase 1" of org.example.stage_type,
+ // "title": "Title for phase 1 of org.example.stage_type"
+ // },
+ // 2: {
+ // "body": "This is a body for phase 2 of org.example.stage_type",
+ // "title": "Title for phase 2 of org.example.stage_type"
+ // "continueText": "Confirm identity with Example Auth",
+ // "continueKind": "danger"
+ // }
+ // }
+ // }
+ aestheticsForStagePhases: PropTypes.object,
},
getInitialState: function() {
return {
authError: null,
+
+ // See _onUpdateStagePhase()
+ uiaStage: null,
+ uiaStagePhase: null,
};
},
@@ -57,12 +83,21 @@ export default createReactClass({
if (success) {
this.props.onFinished(true, result);
} else {
- this.setState({
- authError: result,
- });
+ if (result === ERROR_USER_CANCELLED) {
+ this.props.onFinished(false, null);
+ } else {
+ this.setState({
+ authError: result,
+ });
+ }
}
},
+ _onUpdateStagePhase: function(newStage, newPhase) {
+ // We copy the stage and stage phase params into state for title selection in render()
+ this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
+ },
+
_onDismissClick: function() {
this.props.onFinished(false);
},
@@ -71,6 +106,23 @@ export default createReactClass({
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+ // Let's pick a title, body, and other params text that we'll show to the user. The order
+ // is most specific first, so stagePhase > our props > defaults.
+
+ let title = this.state.authError ? 'Error' : (this.props.title || _t('Authentication'));
+ let body = this.state.authError ? null : this.props.body;
+ let continueText = null;
+ let continueKind = null;
+ if (!this.state.authError && this.props.aestheticsForStagePhases) {
+ if (this.props.aestheticsForStagePhases[this.state.uiaStage]) {
+ const aesthetics = this.props.aestheticsForStagePhases[this.state.uiaStage][this.state.uiaStagePhase];
+ if (aesthetics && aesthetics.title) title = aesthetics.title;
+ if (aesthetics && aesthetics.body) body = aesthetics.body;
+ if (aesthetics && aesthetics.continueText) continueText = aesthetics.continueText;
+ if (aesthetics && aesthetics.continueKind) continueKind = aesthetics.continueKind;
+ }
+ }
+
let content;
if (this.state.authError) {
content = (
@@ -88,11 +140,16 @@ export default createReactClass({
} else {
content = (
-
);
@@ -101,7 +158,7 @@ export default createReactClass({
return (
{ content }
diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js
index 76faf60eef..b9ef851d3a 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.js
+++ b/src/components/views/dialogs/RoomSettingsDialog.js
@@ -36,12 +36,12 @@ export default class RoomSettingsDialog extends React.Component {
onFinished: PropTypes.func.isRequired,
};
- componentWillMount() {
+ componentDidMount() {
this._dispatcherRef = dis.register(this._onAction);
}
componentWillUnmount() {
- dis.unregister(this._dispatcherRef);
+ if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
}
_onAction = (payload) => {
diff --git a/src/components/views/dialogs/RoomUpgradeDialog.js b/src/components/views/dialogs/RoomUpgradeDialog.js
index dc734718d5..c45d82303b 100644
--- a/src/components/views/dialogs/RoomUpgradeDialog.js
+++ b/src/components/views/dialogs/RoomUpgradeDialog.js
@@ -30,7 +30,7 @@ export default createReactClass({
onFinished: PropTypes.func.isRequired,
},
- componentWillMount: async function() {
+ componentDidMount: async function() {
const recommended = await this.props.room.getRecommendedVersion();
this._targetVersion = recommended.version;
this.setState({busy: false});
diff --git a/src/components/views/dialogs/SetPasswordDialog.js b/src/components/views/dialogs/SetPasswordDialog.js
index c48690bb48..fcc6e67656 100644
--- a/src/components/views/dialogs/SetPasswordDialog.js
+++ b/src/components/views/dialogs/SetPasswordDialog.js
@@ -75,8 +75,8 @@ export default createReactClass({
};
},
- componentWillMount: function() {
- console.info('SetPasswordDialog component will mount');
+ componentDidMount: function() {
+ console.info('SetPasswordDialog component did mount');
},
_onPasswordChanged: function(res) {
diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js
index b42a88ceac..cf4735f608 100644
--- a/src/components/views/dialogs/ShareDialog.js
+++ b/src/components/views/dialogs/ShareDialog.js
@@ -121,7 +121,7 @@ export default class ShareDialog extends React.Component {
});
}
- componentWillMount() {
+ componentDidMount() {
if (this.props.target instanceof Room) {
const permalinkCreator = new RoomPermalinkCreator(this.props.target);
permalinkCreator.load();
diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js
index 69ebb72a6f..4cad13b047 100644
--- a/src/components/views/dialogs/UnknownDeviceDialog.js
+++ b/src/components/views/dialogs/UnknownDeviceDialog.js
@@ -87,7 +87,7 @@ export default createReactClass({
onSend: PropTypes.func.isRequired,
},
- componentWillMount: function() {
+ componentDidMount: function() {
MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
},
diff --git a/src/components/views/dialogs/VerificationRequestDialog.js b/src/components/views/dialogs/VerificationRequestDialog.js
index 30bff80f03..bbf3482a41 100644
--- a/src/components/views/dialogs/VerificationRequestDialog.js
+++ b/src/components/views/dialogs/VerificationRequestDialog.js
@@ -48,6 +48,7 @@ export default class VerificationRequestDialog extends React.Component {
verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished}
member={member}
+ inDialog={true}
/>
;
}
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 0a8bf7443b..db738664c8 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -2,6 +2,7 @@
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+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.
@@ -41,12 +42,30 @@ import PersistedElement from "./PersistedElement";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false;
+/**
+ * Does template substitution on a URL (or any string). Variables will be
+ * passed through encodeURIComponent.
+ * @param {string} uriTemplate The path with template variables e.g. '/foo/$bar'.
+ * @param {Object} variables The key/value pairs to replace the template
+ * variables with. E.g. { '$bar': 'baz' }.
+ * @return {string} The result of replacing all template variables e.g. '/foo/baz'.
+ */
+function uriFromTemplate(uriTemplate, variables) {
+ let out = uriTemplate;
+ for (const [key, val] of Object.entries(variables)) {
+ out = out.replace(
+ '$' + key, encodeURIComponent(val),
+ );
+ }
+ return out;
+}
+
export default class AppTile extends React.Component {
constructor(props) {
super(props);
// The key used for PersistedElement
- this._persistKey = 'widget_' + this.props.id;
+ this._persistKey = 'widget_' + this.props.app.id;
this.state = this._getNewState(props);
@@ -78,7 +97,7 @@ export default class AppTile extends React.Component {
// This is a function to make the impact of calling SettingsStore slightly less
const hasPermissionToLoad = () => {
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId);
- return !!currentlyAllowedWidgets[newProps.eventId];
+ return !!currentlyAllowedWidgets[newProps.app.eventId];
};
const PersistedElement = sdk.getComponent("elements.PersistedElement");
@@ -86,7 +105,7 @@ export default class AppTile extends React.Component {
initialising: true, // True while we are mangling the widget URL
// True while the iframe content is loading
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
- widgetUrl: this._addWurlParams(newProps.url),
+ widgetUrl: this._addWurlParams(newProps.app.url),
// Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user
hasPermissionToLoad: newProps.userId === newProps.creatorUserId || hasPermissionToLoad(),
@@ -103,7 +122,7 @@ export default class AppTile extends React.Component {
* @return {Boolean} True if capability supported
*/
_hasCapability(capability) {
- return ActiveWidgetStore.widgetHasCapability(this.props.id, capability);
+ return ActiveWidgetStore.widgetHasCapability(this.props.app.id, capability);
}
/**
@@ -125,7 +144,7 @@ export default class AppTile extends React.Component {
const params = qs.parse(u.query);
// Append widget ID to query parameters
- params.widgetId = this.props.id;
+ params.widgetId = this.props.app.id;
// Append current / parent URL, minus the hash because that will change when
// we view a different room (ie. may change for persistent widgets)
params.parentUrl = window.location.href.split('#', 2)[0];
@@ -137,35 +156,33 @@ export default class AppTile extends React.Component {
isMixedContent() {
const parentContentProtocol = window.location.protocol;
- const u = url.parse(this.props.url);
+ const u = url.parse(this.props.app.url);
const childContentProtocol = u.protocol;
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
console.warn("Refusing to load mixed-content app:",
- parentContentProtocol, childContentProtocol, window.location, this.props.url);
+ parentContentProtocol, childContentProtocol, window.location, this.props.app.url);
return true;
}
return false;
}
- componentWillMount() {
+ componentDidMount() {
// Only fetch IM token on mount if we're showing and have permission to load
if (this.props.show && this.state.hasPermissionToLoad) {
this.setScalarToken();
}
- }
- componentDidMount() {
// Widget action listeners
this.dispatcherRef = dis.register(this._onAction);
}
componentWillUnmount() {
// Widget action listeners
- dis.unregister(this.dispatcherRef);
+ if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
// if it's not remaining on screen, get rid of the PersistedElement container
- if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) {
- ActiveWidgetStore.destroyPersistentWidget(this.props.id);
+ if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
+ ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
const PersistedElement = sdk.getComponent("elements.PersistedElement");
PersistedElement.destroyElement(this._persistKey);
}
@@ -176,11 +193,11 @@ export default class AppTile extends React.Component {
* Component initialisation is only complete when this function has resolved
*/
setScalarToken() {
- if (!WidgetUtils.isScalarUrl(this.props.url)) {
+ if (!WidgetUtils.isScalarUrl(this.props.app.url)) {
console.warn('Non-scalar widget, not setting scalar token!', url);
this.setState({
error: null,
- widgetUrl: this._addWurlParams(this.props.url),
+ widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false,
});
return;
@@ -191,7 +208,7 @@ export default class AppTile extends React.Component {
console.warn("No integration manager - not setting scalar token", url);
this.setState({
error: null,
- widgetUrl: this._addWurlParams(this.props.url),
+ widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false,
});
return;
@@ -204,7 +221,7 @@ export default class AppTile extends React.Component {
console.warn('Non-scalar manager, not setting scalar token!', url);
this.setState({
error: null,
- widgetUrl: this._addWurlParams(this.props.url),
+ widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false,
});
return;
@@ -217,7 +234,7 @@ export default class AppTile extends React.Component {
this._scalarClient.getScalarToken().then((token) => {
// Append scalar_token as a query param if not already present
this._scalarClient.scalarToken = token;
- const u = url.parse(this._addWurlParams(this.props.url));
+ const u = url.parse(this._addWurlParams(this.props.app.url));
const params = qs.parse(u.query);
if (!params.scalar_token) {
params.scalar_token = encodeURIComponent(token);
@@ -246,7 +263,7 @@ export default class AppTile extends React.Component {
}
componentWillReceiveProps(nextProps) {
- if (nextProps.url !== this.props.url) {
+ if (nextProps.app.url !== this.props.app.url) {
this._getNewState(nextProps);
// Fetch IM token for new URL if we're showing and have permission to load
if (this.props.show && this.state.hasPermissionToLoad) {
@@ -280,7 +297,7 @@ export default class AppTile extends React.Component {
}
_onEditClick() {
- console.log("Edit widget ID ", this.props.id);
+ console.log("Edit widget ID ", this.props.app.id);
if (this.props.onEditClick) {
this.props.onEditClick();
} else {
@@ -289,13 +306,13 @@ export default class AppTile extends React.Component {
IntegrationManagers.sharedInstance().openAll(
this.props.room,
'type_' + this.props.type,
- this.props.id,
+ this.props.app.id,
);
} else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.props.room,
'type_' + this.props.type,
- this.props.id,
+ this.props.app.id,
);
}
}
@@ -303,7 +320,7 @@ export default class AppTile extends React.Component {
_onSnapshotClick() {
console.warn("Requesting widget snapshot");
- ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
+ ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
.catch((err) => {
console.error("Failed to get screenshot", err);
})
@@ -351,7 +368,7 @@ export default class AppTile extends React.Component {
WidgetUtils.setRoomWidget(
this.props.room.roomId,
- this.props.id,
+ this.props.app.id,
).catch((e) => {
console.error('Failed to delete widget', e);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -369,7 +386,7 @@ export default class AppTile extends React.Component {
}
_onRevokeClicked() {
- console.info("Revoke widget permissions - %s", this.props.id);
+ console.info("Revoke widget permissions - %s", this.props.app.id);
this._revokeWidgetPermission();
}
@@ -380,10 +397,10 @@ export default class AppTile extends React.Component {
// Destroy the old widget messaging before starting it back up again. Some widgets
// have startup routines that run when they are loaded, so we just need to reinitialize
// the messaging for them.
- ActiveWidgetStore.delWidgetMessaging(this.props.id);
+ ActiveWidgetStore.delWidgetMessaging(this.props.app.id);
this._setupWidgetMessaging();
- ActiveWidgetStore.setRoomId(this.props.id, this.props.room.roomId);
+ ActiveWidgetStore.setRoomId(this.props.app.id, this.props.room.roomId);
this.setState({loading: false});
}
@@ -391,10 +408,10 @@ export default class AppTile extends React.Component {
// FIXME: There's probably no reason to do this here: it should probably be done entirely
// in ActiveWidgetStore.
const widgetMessaging = new WidgetMessaging(
- this.props.id, this.props.url, this.props.userWidget, this._appFrame.current.contentWindow);
- ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
+ this.props.app.id, this._getRenderedUrl(), this.props.userWidget, this._appFrame.current.contentWindow);
+ ActiveWidgetStore.setWidgetMessaging(this.props.app.id, widgetMessaging);
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
- console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
+ console.log(`Widget ${this.props.app.id} requested capabilities: ` + requestedCapabilities);
requestedCapabilities = requestedCapabilities || [];
// Allow whitelisted capabilities
@@ -406,7 +423,7 @@ export default class AppTile extends React.Component {
}, this.props.whitelistCapabilities);
if (requestedWhitelistCapabilies.length > 0 ) {
- console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties: ` +
+ console.warn(`Widget ${this.props.app.id} allowing requested, whitelisted properties: ` +
requestedWhitelistCapabilies,
);
}
@@ -414,7 +431,7 @@ export default class AppTile extends React.Component {
// TODO -- Add UI to warn about and optionally allow requested capabilities
- ActiveWidgetStore.setWidgetCapabilities(this.props.id, requestedWhitelistCapabilies);
+ ActiveWidgetStore.setWidgetCapabilities(this.props.app.id, requestedWhitelistCapabilies);
if (this.props.onCapabilityRequest) {
this.props.onCapabilityRequest(requestedCapabilities);
@@ -422,16 +439,16 @@ 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.type === 'jitsi') {
+ if (this.props.app.type === 'jitsi') {
widgetMessaging.flagReadyToContinue();
}
}).catch((err) => {
- console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
+ console.log(`Failed to get capabilities for widget type ${this.props.app.type}`, this.props.app.id, err);
});
}
_onAction(payload) {
- if (payload.widgetId === this.props.id) {
+ if (payload.widgetId === this.props.app.id) {
switch (payload.action) {
case 'm.sticker':
if (this._hasCapability('m.sticker')) {
@@ -460,9 +477,9 @@ export default class AppTile extends React.Component {
_grantWidgetPermission() {
const roomId = this.props.room.roomId;
- console.info("Granting permission for widget to load: " + this.props.eventId);
+ console.info("Granting permission for widget to load: " + this.props.app.eventId);
const current = SettingsStore.getValue("allowedWidgets", roomId);
- current[this.props.eventId] = true;
+ current[this.props.app.eventId] = true;
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: true});
@@ -476,14 +493,14 @@ export default class AppTile extends React.Component {
_revokeWidgetPermission() {
const roomId = this.props.room.roomId;
- console.info("Revoking permission for widget to load: " + this.props.eventId);
+ console.info("Revoking permission for widget to load: " + this.props.app.eventId);
const current = SettingsStore.getValue("allowedWidgets", roomId);
- current[this.props.eventId] = false;
+ current[this.props.app.eventId] = false;
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: false});
// Force the widget to be non-persistent (able to be deleted/forgotten)
- ActiveWidgetStore.destroyPersistentWidget(this.props.id);
+ ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
const PersistedElement = sdk.getComponent("elements.PersistedElement");
PersistedElement.destroyElement(this._persistKey);
}).catch(err => {
@@ -494,8 +511,8 @@ export default class AppTile extends React.Component {
formatAppTileName() {
let appTileName = "No name";
- if (this.props.name && this.props.name.trim()) {
- appTileName = this.props.name.trim();
+ if (this.props.app.name && this.props.app.name.trim()) {
+ appTileName = this.props.app.name.trim();
}
return appTileName;
}
@@ -519,20 +536,78 @@ export default class AppTile extends React.Component {
}
}
- _getSafeUrl() {
- const parsedWidgetUrl = url.parse(this.state.widgetUrl, true);
+ /**
+ * Replace the widget template variables in a url with their values
+ *
+ * @param {string} u The URL with template variables
+ *
+ * @returns {string} url with temlate variables replaced
+ */
+ _templatedUrl(u) {
+ const myUserId = MatrixClientPeg.get().credentials.userId;
+ const myUser = MatrixClientPeg.get().getUser(myUserId);
+ const vars = Object.assign({
+ domain: "jitsi.riot.im", // v1 widgets have this hardcoded
+ }, this.props.app.data, {
+ 'matrix_user_id': myUserId,
+ 'matrix_room_id': this.props.room.roomId,
+ 'matrix_display_name': myUser ? myUser.displayName : myUserId,
+ 'matrix_avatar_url': myUser ? MatrixClientPeg.get().mxcUrlToHttp(myUser.avatarUrl) : '',
+
+ // TODO: Namespace themes through some standard
+ 'theme': SettingsStore.getValue("theme"),
+ });
+
+ if (vars.conferenceId === undefined) {
+ // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets
+ const parsedUrl = new URL(this.props.app.url);
+ vars.conferenceId = parsedUrl.searchParams.get("confId");
+ }
+
+ return uriFromTemplate(u, vars);
+ }
+
+ /**
+ * Get the URL used in the iframe
+ * In cases where we supply our own UI for a widget, this is an internal
+ * URL different to the one used if the widget is popped out to a separate
+ * tab / browser
+ *
+ * @returns {string} url
+ */
+ _getRenderedUrl() {
+ let url;
+
+ if (this.props.app.type === 'jitsi') {
+ console.log("Replacing Jitsi widget URL with local wrapper");
+ url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
+ url = this._addWurlParams(url);
+ } else {
+ url = this._getSafeUrl(this.state.widgetUrl);
+ }
+ return this._templatedUrl(url);
+ }
+
+ _getPopoutUrl() {
+ if (this.props.app.type === 'jitsi') {
+ return this._templatedUrl(
+ WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
+ );
+ } else {
+ // use app.url, not state.widgetUrl, because we want the one without
+ // the wURL params for the popped-out version.
+ return this._templatedUrl(this._getSafeUrl(this.props.app.url));
+ }
+ }
+
+ _getSafeUrl(u) {
+ const parsedWidgetUrl = url.parse(u, true);
if (ENABLE_REACT_PERF) {
parsedWidgetUrl.search = null;
parsedWidgetUrl.query.react_perf = true;
}
let safeWidgetUrl = '';
- if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol) || (
- // Check if the widget URL is a Jitsi widget in Electron
- parsedWidgetUrl.protocol === 'vector:'
- && parsedWidgetUrl.host === 'vector'
- && parsedWidgetUrl.pathname === '/webapp/jitsi.html'
- && this.props.type === 'jitsi'
- )) {
+ if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol)) {
safeWidgetUrl = url.format(parsedWidgetUrl);
}
return safeWidgetUrl;
@@ -562,9 +637,9 @@ export default class AppTile extends React.Component {
_onPopoutWidgetClick() {
// Using Object.assign workaround as the following opens in a new window instead of a new tab.
- // window.open(this._getSafeUrl(), '_blank', 'noopener=yes');
+ // window.open(this._getPopoutUrl(), '_blank', 'noopener=yes');
Object.assign(document.createElement('a'),
- { target: '_blank', href: this._getSafeUrl(), rel: 'noreferrer noopener'}).click();
+ { target: '_blank', href: this._getPopoutUrl(), rel: 'noreferrer noopener'}).click();
}
_onReloadWidgetClick() {
@@ -641,7 +716,7 @@ export default class AppTile extends React.Component {
@@ -706,7 +781,7 @@ export default class AppTile extends React.Component {
}
return
-
+
{ this.props.showMenubar &&
@@ -753,12 +828,8 @@ export default class AppTile extends React.Component {
AppTile.displayName = 'AppTile';
AppTile.propTypes = {
- id: PropTypes.string.isRequired,
- eventId: PropTypes.string, // required for room widgets
- url: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
+ app: PropTypes.object.isRequired,
room: PropTypes.object.isRequired,
- type: PropTypes.string.isRequired,
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth: PropTypes.bool,
@@ -805,7 +876,6 @@ AppTile.propTypes = {
};
AppTile.defaultProps = {
- url: "",
waitForIframeLoad: true,
showMenubar: true,
showTitle: true,
diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js
index b4d78bb9bc..7328d50328 100644
--- a/src/components/views/elements/DeviceVerifyButtons.js
+++ b/src/components/views/elements/DeviceVerifyButtons.js
@@ -38,7 +38,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
const cli = MatrixClientPeg.get();
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
},
diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js
index 57e1b3d2cd..bbc5560557 100644
--- a/src/components/views/elements/EditableTextContainer.js
+++ b/src/components/views/elements/EditableTextContainer.js
@@ -42,7 +42,7 @@ export default class EditableTextContainer extends React.Component {
this._onValueChanged = this._onValueChanged.bind(this);
}
- componentWillMount() {
+ componentDidMount() {
if (this.props.getInitialValue === undefined) {
// use whatever was given in the initialValue property.
return;
diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js
index cb4e2e4da6..18a7e95e85 100644
--- a/src/components/views/elements/LanguageDropdown.js
+++ b/src/components/views/elements/LanguageDropdown.js
@@ -24,8 +24,8 @@ import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
function languageMatchesSearchQuery(query, language) {
- if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
- if (language.value.toUpperCase() == query.toUpperCase()) return true;
+ if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
+ if (language.value.toUpperCase() === query.toUpperCase()) return true;
return false;
}
@@ -40,7 +40,7 @@ export default class LanguageDropdown extends React.Component {
};
}
- componentWillMount() {
+ componentDidMount() {
languageHandler.getAllLanguagesFromJson().then((langs) => {
langs.sort(function(a, b) {
if (a.label < b.label) return -1;
diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js
index a807ed3b93..a146debc45 100644
--- a/src/components/views/elements/PersistentApp.js
+++ b/src/components/views/elements/PersistentApp.js
@@ -1,6 +1,6 @@
/*
Copyright 2018 New Vector Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 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.
@@ -33,7 +33,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
},
@@ -75,11 +75,7 @@ export default createReactClass({
const AppTile = sdk.getComponent('elements.AppTile');
return ;
}
- componentWillMount() {
+ componentDidMount() {
this.unmounted = false;
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
this.room.on("Room.redaction", this.onRoomRedaction);
diff --git a/src/components/views/elements/TintableSvg.js b/src/components/views/elements/TintableSvg.js
index 3e0e41f411..66625c7b87 100644
--- a/src/components/views/elements/TintableSvg.js
+++ b/src/components/views/elements/TintableSvg.js
@@ -36,11 +36,9 @@ const TintableSvg = createReactClass({
idSequence: 0,
},
- componentWillMount: function() {
- this.fixups = [];
- },
-
componentDidMount: function() {
+ this.fixups = [];
+
this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this;
},
diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js
index f70c769ad7..95517ef548 100644
--- a/src/components/views/groups/GroupMemberInfo.js
+++ b/src/components/views/groups/GroupMemberInfo.js
@@ -49,7 +49,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js
index 2853e70afa..ca374d1309 100644
--- a/src/components/views/groups/GroupMemberList.js
+++ b/src/components/views/groups/GroupMemberList.js
@@ -47,7 +47,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js
index 602f9036d8..81f0f469ef 100644
--- a/src/components/views/groups/GroupPublicityToggle.js
+++ b/src/components/views/groups/GroupPublicityToggle.js
@@ -36,7 +36,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._initGroupStore(this.props.groupId);
},
diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js
index 91d84be4d1..789396e719 100644
--- a/src/components/views/groups/GroupRoomInfo.js
+++ b/src/components/views/groups/GroupRoomInfo.js
@@ -47,7 +47,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._initGroupStore(this.props.groupId);
},
diff --git a/src/components/views/groups/GroupRoomList.js b/src/components/views/groups/GroupRoomList.js
index dee304e1f6..5c3e1587db 100644
--- a/src/components/views/groups/GroupRoomList.js
+++ b/src/components/views/groups/GroupRoomList.js
@@ -39,7 +39,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
index 12e2427e69..b845a83c2a 100644
--- a/src/components/views/groups/GroupTile.js
+++ b/src/components/views/groups/GroupTile.js
@@ -55,7 +55,7 @@ const GroupTile = createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
this.setState({profile});
}).catch((err) => {
diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js
index a65d23bed7..8f57eccd05 100644
--- a/src/components/views/groups/GroupUserSettings.js
+++ b/src/components/views/groups/GroupUserSettings.js
@@ -34,7 +34,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null});
}, (err) => {
diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js
index 64f2611caf..ad238a728e 100644
--- a/src/components/views/messages/MImageBody.js
+++ b/src/components/views/messages/MImageBody.js
@@ -67,11 +67,6 @@ export default class MImageBody extends React.Component {
this._image = createRef();
}
- componentWillMount() {
- this.unmounted = false;
- this.context.on('sync', this.onClientSync);
- }
-
// FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
onClientSync(syncState, prevState) {
if (this.unmounted) return;
@@ -258,6 +253,9 @@ export default class MImageBody extends React.Component {
}
componentDidMount() {
+ this.unmounted = false;
+ this.context.on('sync', this.onClientSync);
+
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null);
diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js
index f7da1029e1..bed93b68c3 100644
--- a/src/components/views/messages/SenderProfile.js
+++ b/src/components/views/messages/SenderProfile.js
@@ -42,7 +42,7 @@ export default createReactClass({
};
},
- componentWillMount() {
+ componentDidMount() {
this.unmounted = false;
this._updateRelatedGroups();
diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js
index 2c51662111..6f884c4abf 100644
--- a/src/components/views/right_panel/EncryptionPanel.js
+++ b/src/components/views/right_panel/EncryptionPanel.js
@@ -31,7 +31,7 @@ import {_t} from "../../../languageHandler";
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
const EncryptionPanel = (props) => {
- const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted} = props;
+ const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted, inDialog} = props;
const [request, setRequest] = useState(verificationRequest);
// state to show a spinner immediately after clicking "start verification",
// before we have a request
@@ -133,6 +133,7 @@ const EncryptionPanel = (props) => {
member={member}
request={request}
key={request.channel.transactionId}
+ inDialog={inDialog}
phase={phase} />
);
}
@@ -142,6 +143,7 @@ EncryptionPanel.propTypes = {
onClose: PropTypes.func.isRequired,
verificationRequest: PropTypes.object,
layout: PropTypes.string,
+ inDialog: PropTypes.bool,
};
export default EncryptionPanel;
diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.js
index dbcae4529a..03b03218ee 100644
--- a/src/components/views/right_panel/HeaderButtons.js
+++ b/src/components/views/right_panel/HeaderButtons.js
@@ -40,7 +40,7 @@ export default class HeaderButtons extends React.Component {
};
}
- componentWillMount() {
+ componentDidMount() {
this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this));
this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses
}
diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js
index 1cb2737005..7ba1fb829a 100644
--- a/src/components/views/right_panel/VerificationPanel.js
+++ b/src/components/views/right_panel/VerificationPanel.js
@@ -245,6 +245,7 @@ export default class VerificationPanel extends React.PureComponent {
sas={this.state.sasEvent.sas}
onCancel={this._onSasMismatchesClick}
onDone={this._onSasMatchesClick}
+ inDialog={this.props.inDialog}
/>
;
} else {
diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js
index f81a5630a4..785d7ecccb 100644
--- a/src/components/views/rooms/AppsDrawer.js
+++ b/src/components/views/rooms/AppsDrawer.js
@@ -55,13 +55,10 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps);
- },
-
- componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
@@ -71,7 +68,7 @@ export default createReactClass({
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
WidgetEchoStore.removeListener('update', this._updateApps);
- dis.unregister(this.dispatcherRef);
+ if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
},
componentWillReceiveProps(newProps) {
@@ -160,11 +157,7 @@ export default createReactClass({
return (
{
console.error("Error deleting sessions", e);
diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js
index 203a7ee46e..c9c6a5ec4f 100644
--- a/src/components/views/settings/EventIndexPanel.js
+++ b/src/components/views/settings/EventIndexPanel.js
@@ -63,7 +63,7 @@ export default class EventIndexPanel extends React.Component {
}
}
- async componentWillMount(): void {
+ async componentDidMount(): void {
this.updateState();
}
diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js
index 27fdb2cb56..9d60ed1188 100644
--- a/src/components/views/settings/KeyBackupPanel.js
+++ b/src/components/views/settings/KeyBackupPanel.js
@@ -38,7 +38,7 @@ export default class KeyBackupPanel extends React.PureComponent {
};
}
- componentWillMount() {
+ componentDidMount() {
this._checkKeyBackupStatus();
MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatus);
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index 1f4bde6eab..a3173f18bb 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -87,7 +87,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._refreshFromServer();
},
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
index 9a2db8113e..146d841d58 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
@@ -40,7 +40,7 @@ export default class HelpUserSettingsTab extends React.Component {
};
}
- componentWillMount(): void {
+ componentDidMount(): void {
PlatformPeg.get().getAppVersion().then((ver) => this.setState({vectorVersion: ver})).catch((e) => {
console.error("Error getting vector version: ", e);
});
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
index bdcb25dd51..bdb2a9ffc4 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
@@ -81,7 +81,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
};
}
- async componentWillMount(): void {
+ async componentDidMount(): void {
const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch();
diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js
index e640a75129..9b4ea4f2bd 100644
--- a/src/components/views/verification/VerificationShowSas.js
+++ b/src/components/views/verification/VerificationShowSas.js
@@ -33,6 +33,7 @@ export default class VerificationShowSas extends React.Component {
onCancel: PropTypes.func.isRequired,
sas: PropTypes.object.isRequired,
isSelf: PropTypes.bool,
+ inDialog: PropTypes.bool, // whether this component is being shown in a dialog and to use DialogButtons
};
constructor(props) {
@@ -112,7 +113,7 @@ export default class VerificationShowSas extends React.Component {
text = _t("Cancelling…");
}
confirm = ;
- } else {
+ } else if (this.props.inDialog) {
// FIXME: stop using DialogButtons here once this component is only used in the right panel verification
confirm = ;
+ } else {
+ confirm =
+
+ { _t("They match") }
+
+
+ { _t("They don't match") }
+
+ ;
}
return
diff --git a/src/components/views/voip/CallPreview.js b/src/components/views/voip/CallPreview.js
index 57bf35a719..049dd8a3c6 100644
--- a/src/components/views/voip/CallPreview.js
+++ b/src/components/views/voip/CallPreview.js
@@ -40,7 +40,7 @@ export default createReactClass({
};
},
- componentWillMount: function() {
+ componentDidMount: function() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this.dispatcherRef = dis.register(this._onAction);
},
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 0e9aa3c83f..24a6568d82 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1,8 +1,17 @@
{
"This email address is already in use": "This email address is already in use",
"This phone number is already in use": "This phone number is already in use",
+ "Use Single Sign On to continue": "Use Single Sign On to continue",
+ "Confirm adding this email address by using Single Sign On to prove your identity.": "Confirm adding this email address by using Single Sign On to prove your identity.",
+ "Single Sign On": "Single Sign On",
+ "Confirm adding email": "Confirm adding email",
+ "Click the button below to confirm adding this email address.": "Click the button below to confirm adding this email address.",
+ "Confirm": "Confirm",
"Add Email Address": "Add Email Address",
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
+ "Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.",
+ "Confirm adding phone number": "Confirm adding phone number",
+ "Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.",
"Add Phone Number": "Add Phone Number",
"The platform you're on": "The platform you're on",
"The version of Riot": "The version of Riot",
@@ -599,6 +608,10 @@
"up to date": "up to date",
"Your homeserver does not support session management.": "Your homeserver does not support session management.",
"Unable to load session list": "Unable to load session list",
+ "Confirm deleting these sessions by using Single Sign On to prove your identity.": "Confirm deleting these sessions by using Single Sign On to prove your identity.",
+ "Confirm deleting these sessions": "Confirm deleting these sessions",
+ "Click the button below to confirm deleting these sessions.": "Click the button below to confirm deleting these sessions.",
+ "Delete sessions": "Delete sessions",
"Authentication": "Authentication",
"Delete %(count)s sessions|other": "Delete %(count)s sessions",
"Delete %(count)s sessions|one": "Delete %(count)s session",
@@ -1862,7 +1875,6 @@
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
"Enter username": "Enter username",
"Email (optional)": "Email (optional)",
- "Confirm": "Confirm",
"Phone (optional)": "Phone (optional)",
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
"Create your Matrix account on
": "Create your Matrix account on
",
diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js
index 14257af014..69939d0f32 100644
--- a/src/indexing/EventIndex.js
+++ b/src/indexing/EventIndex.js
@@ -109,6 +109,7 @@ export default class EventIndex extends EventEmitter {
roomId: room.roomId,
token: token,
direction: "b",
+ fullCrawl: true,
};
const forwardCheckpoint = {
diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js
index eea995cfea..9fb6358c1f 100644
--- a/src/utils/WidgetUtils.js
+++ b/src/utils/WidgetUtils.js
@@ -30,26 +30,6 @@ import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {Capability} from "../widgets/WidgetApi";
-/**
- * Encodes a URI according to a set of template variables. Variables will be
- * passed through encodeURIComponent.
- * @param {string} pathTemplate The path with template variables e.g. '/foo/$bar'.
- * @param {Object} variables The key/value pairs to replace the template
- * variables with. E.g. { '$bar': 'baz' }.
- * @return {string} The result of replacing all template variables e.g. '/foo/baz'.
- */
-function encodeUri(pathTemplate, variables) {
- for (const key in variables) {
- if (!variables.hasOwnProperty(key)) {
- continue;
- }
- pathTemplate = pathTemplate.replace(
- key, encodeURIComponent(variables[key]),
- );
- }
- return pathTemplate;
-}
-
export default class WidgetUtils {
/* Returns true if user is able to send state events to modify widgets in this room
* (Does not apply to non-room-based / user widgets)
@@ -402,18 +382,6 @@ export default class WidgetUtils {
}
static makeAppConfig(appId, app, senderUserId, roomId, eventId) {
- const myUserId = MatrixClientPeg.get().credentials.userId;
- const user = MatrixClientPeg.get().getUser(myUserId);
- const params = {
- '$matrix_user_id': myUserId,
- '$matrix_room_id': roomId,
- '$matrix_display_name': user ? user.displayName : myUserId,
- '$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
-
- // TODO: Namespace themes through some standard
- '$theme': SettingsStore.getValue("theme"),
- };
-
if (!senderUserId) {
throw new Error("Widgets must be created by someone - provide a senderUserId");
}
@@ -423,32 +391,6 @@ export default class WidgetUtils {
app.eventId = eventId;
app.name = app.name || app.type;
- if (app.type === 'jitsi') {
- console.log("Replacing Jitsi widget URL with local wrapper");
- if (!app.data || !app.data.conferenceId) {
- // Assumed to be a v1 widget: add a data object for visibility on the wrapper
- // TODO: Remove this once mobile supports v2 widgets
- console.log("Replacing v1 Jitsi widget with v2 equivalent");
- const parsed = new URL(app.url);
- app.data = {
- conferenceId: parsed.searchParams.get("confId"),
- domain: "jitsi.riot.im", // v1 widgets have this hardcoded
- };
- }
-
- app.url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
- }
-
- if (app.data) {
- Object.keys(app.data).forEach((key) => {
- params['$' + key] = app.data[key];
- });
-
- app.waitForIframeLoad = (app.data.waitForIframeLoad === 'false' ? false : true);
- }
-
- app.url = encodeUri(app.url, params);
-
return app;
}
@@ -462,7 +404,6 @@ export default class WidgetUtils {
// widgets from at all, but it probably makes sense for sanity.
if (appType === 'jitsi') {
capWhitelist.push(Capability.AlwaysOnScreen);
- capWhitelist.push(Capability.GetRiotWebConfig);
}
return capWhitelist;
diff --git a/src/verification.js b/src/verification.js
index 2231346478..3170eb8825 100644
--- a/src/verification.js
+++ b/src/verification.js
@@ -99,7 +99,7 @@ export async function legacyVerifyUser(user) {
return;
}
const cli = MatrixClientPeg.get();
- const verificationRequestPromise = cli.beginKeyVerification(user.userId);
+ const verificationRequestPromise = cli.requestVerification(user.userId);
dis.dispatch({
action: "set_right_panel_phase",
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts
index d6d1c79a99..05237d258f 100644
--- a/src/widgets/WidgetApi.ts
+++ b/src/widgets/WidgetApi.ts
@@ -23,7 +23,6 @@ export enum Capability {
Screenshot = "m.capability.screenshot",
Sticker = "m.sticker",
AlwaysOnScreen = "m.always_on_screen",
- GetRiotWebConfig = "im.vector.web.riot_config",
}
export enum KnownWidgetActions {
@@ -34,7 +33,6 @@ export enum KnownWidgetActions {
UpdateVisibility = "visibility",
ReceiveOpenIDCredentials = "openid_credentials",
SetAlwaysOnScreen = "set_always_on_screen",
- GetRiotWebConfig = "im.vector.web.riot_config",
ClientReady = "im.vector.ready",
}
@@ -157,12 +155,4 @@ export class WidgetApi {
resolve(); // SetAlwaysOnScreen is currently fire-and-forget, but that could change.
});
}
-
- public getRiotConfig(): Promise
{
- return new Promise(resolve => {
- this.callAction(KnownWidgetActions.GetRiotWebConfig, {}, response => {
- resolve(response.response.config);
- });
- });
- }
}
diff --git a/test/components/views/rooms/RoomSettings-test.js b/test/components/views/rooms/RoomSettings-test.js
index 870d7f0aab..5e21f729d0 100644
--- a/test/components/views/rooms/RoomSettings-test.js
+++ b/test/components/views/rooms/RoomSettings-test.js
@@ -134,7 +134,7 @@ describe.skip('RoomSettings', () => {
});
});
- // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount`
+ // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentDidMount`
xit('should set room directory publicity when set to true', (done) => {
const isRoomPublished = true;
roomSettings.setState({
diff --git a/yarn.lock b/yarn.lock
index c5fc8268a1..acd474ffd7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5691,7 +5691,7 @@ mathml-tag-names@^2.0.1:
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "5.2.0"
- resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/223d37ffce674a23ca73702f04b9ba31cfd84196"
+ resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/934ed37fdc90948273d7da3ec9f8728195c78a63"
dependencies:
"@babel/runtime" "^7.8.3"
another-json "^0.2.0"