You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-11-17 17:42:41 +03:00
Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/cs_verification_decoration
Conflicts: src/components/views/right_panel/VerificationPanel.js
This commit is contained in:
@@ -20,7 +20,7 @@ import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {getEntryComponentForLoginType} from '../views/auth/InteractiveAuthEntryComponents';
|
||||
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
||||
|
||||
import * as sdk from '../../index';
|
||||
|
||||
|
||||
@@ -89,12 +89,15 @@ export const VIEWS = {
|
||||
// showing flow to trust this new device with cross-signing
|
||||
COMPLETE_SECURITY: 6,
|
||||
|
||||
// flow to setup SSSS / cross-signing on this account
|
||||
E2E_SETUP: 7,
|
||||
|
||||
// we are logged in with an active matrix client.
|
||||
LOGGED_IN: 7,
|
||||
LOGGED_IN: 8,
|
||||
|
||||
// We are logged out (invalid token) but have our local state again. The user
|
||||
// should log back in to rehydrate the client.
|
||||
SOFT_LOGOUT: 8,
|
||||
SOFT_LOGOUT: 9,
|
||||
};
|
||||
|
||||
// Actions that are redirected through the onboarding process prior to being
|
||||
@@ -253,6 +256,9 @@ export default createReactClass({
|
||||
// logout page.
|
||||
Lifecycle.loadSession({});
|
||||
}
|
||||
|
||||
this._accountPassword = null;
|
||||
this._accountPasswordTimer = null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -349,6 +355,8 @@ export default createReactClass({
|
||||
window.removeEventListener("focus", this.onFocus);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
|
||||
|
||||
if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer);
|
||||
},
|
||||
|
||||
componentWillUpdate: function(props, state) {
|
||||
@@ -657,7 +665,9 @@ export default createReactClass({
|
||||
if (
|
||||
!Lifecycle.isSoftLogout() &&
|
||||
this.state.view !== VIEWS.LOGIN &&
|
||||
this.state.view !== VIEWS.COMPLETE_SECURITY
|
||||
this.state.view !== VIEWS.REGISTER &&
|
||||
this.state.view !== VIEWS.COMPLETE_SECURITY &&
|
||||
this.state.view !== VIEWS.E2E_SETUP
|
||||
) {
|
||||
this._onLoggedIn();
|
||||
}
|
||||
@@ -1724,6 +1734,10 @@ export default createReactClass({
|
||||
this.showScreen("forgot_password");
|
||||
},
|
||||
|
||||
onRegisterFlowComplete: function(credentials, password) {
|
||||
return this.onUserCompletedLoginFlow(credentials, password);
|
||||
},
|
||||
|
||||
// returns a promise which resolves to the new MatrixClient
|
||||
onRegistered: function(credentials) {
|
||||
return Lifecycle.setLoggedIn(credentials);
|
||||
@@ -1812,7 +1826,14 @@ export default createReactClass({
|
||||
this._loggedInView = ref;
|
||||
},
|
||||
|
||||
async onUserCompletedLoginFlow(credentials) {
|
||||
async onUserCompletedLoginFlow(credentials, password) {
|
||||
this._accountPassword = password;
|
||||
// self-destruct the password after 5mins
|
||||
if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer);
|
||||
this._accountPasswordTimer = setTimeout(() => {
|
||||
this._accountPassword = null;
|
||||
this._accountPasswordTimer = null;
|
||||
}, 60 * 5 * 1000);
|
||||
// Wait for the client to be logged in (but not started)
|
||||
// which is enough to ask the server about account data.
|
||||
const loggedIn = new Promise(resolve => {
|
||||
@@ -1826,7 +1847,7 @@ export default createReactClass({
|
||||
});
|
||||
|
||||
// Create and start the client in the background
|
||||
Lifecycle.setLoggedIn(credentials);
|
||||
const setLoggedInPromise = Lifecycle.setLoggedIn(credentials);
|
||||
await loggedIn;
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
@@ -1847,12 +1868,20 @@ export default createReactClass({
|
||||
|
||||
if (masterKeyInStorage) {
|
||||
this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY });
|
||||
} else if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
// This will only work if the feature is set to 'enable' in the config,
|
||||
// since it's too early in the lifecycle for users to have turned the
|
||||
// labs flag on.
|
||||
this.setStateForNewView({ view: VIEWS.E2E_SETUP });
|
||||
} else {
|
||||
this._onLoggedIn();
|
||||
}
|
||||
|
||||
return setLoggedInPromise;
|
||||
},
|
||||
|
||||
onCompleteSecurityFinished() {
|
||||
// complete security / e2e setup has finished
|
||||
onCompleteSecurityE2eSetupFinished() {
|
||||
this._onLoggedIn();
|
||||
},
|
||||
|
||||
@@ -1872,7 +1901,15 @@ export default createReactClass({
|
||||
const CompleteSecurity = sdk.getComponent('structures.auth.CompleteSecurity');
|
||||
view = (
|
||||
<CompleteSecurity
|
||||
onFinished={this.onCompleteSecurityFinished}
|
||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.view === VIEWS.E2E_SETUP) {
|
||||
const E2eSetup = sdk.getComponent('structures.auth.E2eSetup');
|
||||
view = (
|
||||
<E2eSetup
|
||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||
accountPassword={this._accountPassword}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.view === VIEWS.POST_REGISTRATION) {
|
||||
@@ -1939,7 +1976,7 @@ export default createReactClass({
|
||||
email={this.props.startingFragmentQueryParams.email}
|
||||
brand={this.props.config.brand}
|
||||
makeRegistrationUrl={this._makeRegistrationUrl}
|
||||
onLoggedIn={this.onRegistered}
|
||||
onLoggedIn={this.onRegisterFlowComplete}
|
||||
onLoginClick={this.onLoginClick}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
{...this.getServerProperties()}
|
||||
|
||||
@@ -766,7 +766,7 @@ export default createReactClass({
|
||||
|
||||
onUserVerificationChanged: function(userId, _trustStatus) {
|
||||
const room = this.state.room;
|
||||
if (!room.currentState.getMember(userId)) {
|
||||
if (!room || !room.currentState.getMember(userId)) {
|
||||
return;
|
||||
}
|
||||
this._updateE2EStatus(room);
|
||||
|
||||
@@ -23,9 +23,11 @@ export default class ToastContainer extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {toasts: ToastStore.sharedInstance().getToasts()};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Start listening here rather than in componentDidMount because
|
||||
// toasts may dismiss themselves in their didMount if they find
|
||||
// they're already irrelevant by the time they're mounted, and
|
||||
// our own componentDidMount is too late.
|
||||
ToastStore.sharedInstance().on('update', this._onToastStoreUpdate);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {TopLeftMenu} from '../views/context_menus/TopLeftMenu';
|
||||
import TopLeftMenu from '../views/context_menus/TopLeftMenu';
|
||||
import BaseAvatar from '../views/avatars/BaseAvatar';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import * as Avatar from '../../Avatar';
|
||||
|
||||
@@ -35,7 +35,21 @@ export default class CompleteSecurity extends React.Component {
|
||||
|
||||
this.state = {
|
||||
phase: PHASE_INTRO,
|
||||
// this serves dual purpose as the object for the request logic and
|
||||
// the presence of it insidicating that we're in 'verify mode'.
|
||||
// Because of the latter, it lives in the state.
|
||||
verificationRequest: null,
|
||||
};
|
||||
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.verificationRequest) {
|
||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||
}
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
||||
}
|
||||
}
|
||||
|
||||
onStartClick = async () => {
|
||||
@@ -55,6 +69,27 @@ export default class CompleteSecurity extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onVerificationRequest = (request) => {
|
||||
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
||||
|
||||
if (this.state.verificationRequest) {
|
||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||
}
|
||||
request.on("change", this.onVerificationRequestChange);
|
||||
this.setState({
|
||||
verificationRequest: request,
|
||||
});
|
||||
}
|
||||
|
||||
onVerificationRequestChange = () => {
|
||||
if (this.state.verificationRequest.cancelled) {
|
||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||
this.setState({
|
||||
verificationRequest: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSkipClick = () => {
|
||||
this.setState({
|
||||
phase: PHASE_CONFIRM_SKIP,
|
||||
@@ -87,7 +122,13 @@ export default class CompleteSecurity extends React.Component {
|
||||
let icon;
|
||||
let title;
|
||||
let body;
|
||||
if (phase === PHASE_INTRO) {
|
||||
|
||||
if (this.state.verificationRequest) {
|
||||
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
|
||||
body = <IncomingSasDialog verifier={this.state.verificationRequest.verifier}
|
||||
onFinished={this.props.onFinished}
|
||||
/>;
|
||||
} else if (phase === PHASE_INTRO) {
|
||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
||||
title = _t("Complete security");
|
||||
body = (
|
||||
|
||||
50
src/components/structures/auth/E2eSetup.js
Normal file
50
src/components/structures/auth/E2eSetup.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AsyncWrapper from '../../../AsyncWrapper';
|
||||
import * as sdk from '../../../index';
|
||||
|
||||
export default class E2eSetup extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
accountPassword: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// awkwardly indented because https://github.com/eslint/eslint/issues/11310
|
||||
this._createStorageDialogPromise =
|
||||
import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog");
|
||||
}
|
||||
|
||||
render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthBody header={false}>
|
||||
<AsyncWrapper prom={this._createStorageDialogPromise}
|
||||
hasCancel={false}
|
||||
onFinished={this.props.onFinished}
|
||||
accountPassword={this.props.accountPassword}
|
||||
/>
|
||||
</AuthBody>
|
||||
</AuthPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,11 @@ export default createReactClass({
|
||||
displayName: 'Login',
|
||||
|
||||
propTypes: {
|
||||
// Called when the user has logged in. Params:
|
||||
// - The object returned by the login API
|
||||
// - The user's password, if applicable, (may be cached in memory for a
|
||||
// short time so the user is not required to re-enter their password
|
||||
// for operations like uploading cross-signing keys).
|
||||
onLoggedIn: PropTypes.func.isRequired,
|
||||
|
||||
// If true, the component will consider itself busy.
|
||||
@@ -181,7 +186,7 @@ export default createReactClass({
|
||||
username, phoneCountry, phoneNumber, password,
|
||||
).then((data) => {
|
||||
this.setState({serverIsAlive: true}); // it must be, we logged in.
|
||||
this.props.onLoggedIn(data);
|
||||
this.props.onLoggedIn(data, password);
|
||||
}, (error) => {
|
||||
if (this._unmounted) {
|
||||
return;
|
||||
|
||||
@@ -45,7 +45,13 @@ export default createReactClass({
|
||||
displayName: 'Registration',
|
||||
|
||||
propTypes: {
|
||||
// Called when the user has logged in. Params:
|
||||
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
|
||||
// - The user's password, if available and applicable (may be cached in memory
|
||||
// for a short time so the user is not required to re-enter their password
|
||||
// for operations like uploading cross-signing keys).
|
||||
onLoggedIn: PropTypes.func.isRequired,
|
||||
|
||||
clientSecret: PropTypes.string,
|
||||
sessionId: PropTypes.string,
|
||||
makeRegistrationUrl: PropTypes.func.isRequired,
|
||||
@@ -348,7 +354,7 @@ export default createReactClass({
|
||||
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token,
|
||||
});
|
||||
}, this.state.formVals.password);
|
||||
|
||||
this._setupPushers(cli);
|
||||
// we're still busy until we get unmounted: don't show the registration form again
|
||||
|
||||
@@ -33,6 +33,10 @@ export default class AuthBody extends React.PureComponent {
|
||||
const classes = {
|
||||
'mx_AuthBody': true,
|
||||
'mx_AuthBody_noHeader': !this.props.header,
|
||||
// XXX The login pages all use a smaller fonts size but we don't want this
|
||||
// for subsequent auth screens like the e2e setup. Doing this a terrible way
|
||||
// for now.
|
||||
'mx_AuthBody_loginRegister': this.props.header,
|
||||
};
|
||||
|
||||
return <div className={classnames(classes)}>
|
||||
|
||||
@@ -641,7 +641,7 @@ const AuthEntryComponents = [
|
||||
TermsAuthEntry,
|
||||
];
|
||||
|
||||
export function getEntryComponentForLoginType(loginType) {
|
||||
export default function getEntryComponentForLoginType(loginType) {
|
||||
for (const c of AuthEntryComponents) {
|
||||
if (c.LOGIN_TYPE == loginType) {
|
||||
return c;
|
||||
|
||||
@@ -27,7 +27,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
import * as sdk from "../../../index";
|
||||
|
||||
export class TopLeftMenu extends React.Component {
|
||||
export default class TopLeftMenu extends React.Component {
|
||||
static propTypes = {
|
||||
displayName: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../../index';
|
||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
@@ -32,6 +33,16 @@ const RESTORE_TYPE_SECRET_STORAGE = 2;
|
||||
* Dialog for restoring e2e keys from a backup and the user's recovery key
|
||||
*/
|
||||
export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// if false, will close the dialog as soon as the restore completes succesfully
|
||||
// default: true
|
||||
showSummary: PropTypes.bool,
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
showSummary: true,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -96,6 +107,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
|
||||
this.state.passPhrase, undefined, undefined, this.state.backupInfo,
|
||||
);
|
||||
if (!this.props.showSummary) {
|
||||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
loading: false,
|
||||
recoverInfo,
|
||||
@@ -119,6 +134,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
|
||||
this.state.recoveryKey, undefined, undefined, this.state.backupInfo,
|
||||
);
|
||||
if (!this.props.showSummary) {
|
||||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
loading: false,
|
||||
recoverInfo,
|
||||
@@ -253,6 +272,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||
title = _t("Error");
|
||||
content = _t("No backup found!");
|
||||
} else if (this.state.recoverInfo) {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
title = _t("Backup Restored");
|
||||
let failedToDecrypt;
|
||||
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
|
||||
@@ -264,6 +284,11 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||
content = <div>
|
||||
<p>{_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}</p>
|
||||
{failedToDecrypt}
|
||||
<DialogButtons primaryButton={_t('OK')}
|
||||
onPrimaryButtonClick={this._onDone}
|
||||
hasCancel={false}
|
||||
focus={true}
|
||||
/>
|
||||
</div>;
|
||||
} else if (backupHasPassphrase && !this.state.forceRecoveryKey) {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
@@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import * as recent from './recent';
|
||||
import * as recent from '../../../emojipicker/recent';
|
||||
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
||||
|
||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 Tulir Asokan <tulir@maunium.net>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
const REACTION_COUNT = JSON.parse(window.localStorage.mx_reaction_count || '{}');
|
||||
let sorted = null;
|
||||
|
||||
export function add(emoji) {
|
||||
const [count] = REACTION_COUNT[emoji] || [0];
|
||||
REACTION_COUNT[emoji] = [count + 1, Date.now()];
|
||||
window.localStorage.mx_reaction_count = JSON.stringify(REACTION_COUNT);
|
||||
sorted = null;
|
||||
}
|
||||
|
||||
export function get(limit = 24) {
|
||||
if (sorted === null) {
|
||||
sorted = Object.entries(REACTION_COUNT)
|
||||
.sort(([, [count1, date1]], [, [count2, date2]]) =>
|
||||
count2 === count1 ? date2 - date1 : count2 - count1)
|
||||
.map(([emoji, count]) => emoji);
|
||||
}
|
||||
return sorted.slice(0, limit);
|
||||
}
|
||||
@@ -363,7 +363,7 @@ export default class RoomBreadcrumbs extends React.Component {
|
||||
}
|
||||
|
||||
let dmIndicator;
|
||||
if (this._isDmRoom(r.room)) {
|
||||
if (this._isDmRoom(r.room) && !SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
dmIndicator = <img
|
||||
src={require("../../../../res/img/icon_person.svg")}
|
||||
className="mx_RoomBreadcrumbs_dmIndicator"
|
||||
|
||||
@@ -31,6 +31,7 @@ import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||
import {CancelButton} from './SimpleRoomHeader';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import InviteOnlyIcon from './InviteOnlyIcon';
|
||||
|
||||
@@ -161,10 +162,12 @@ export default createReactClass({
|
||||
<E2EIcon status={this.props.e2eStatus} /> :
|
||||
undefined;
|
||||
|
||||
const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
||||
const joinRules = this.props.room && this.props.room.currentState.getStateEvents("m.room.join_rules", "");
|
||||
const joinRule = joinRules && joinRules.getContent().join_rule;
|
||||
let privateIcon;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
// Don't show an invite-only icon for DMs. Users know they're invite-only.
|
||||
if (!dmUserId && SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
if (joinRule == "invite") {
|
||||
privateIcon = <InviteOnlyIcon />;
|
||||
}
|
||||
|
||||
@@ -478,8 +478,9 @@ export default createReactClass({
|
||||
|
||||
let dmIndicator;
|
||||
let dmOnline;
|
||||
// If we can place a shield, do that instead
|
||||
if (dmUserId && !this.state.e2eStatus) {
|
||||
/* Post-cross-signing we don't show DM indicators at all, instead relying on user
|
||||
context to let them know when that is. */
|
||||
if (dmUserId && !SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
dmIndicator = <img
|
||||
src={require("../../../../res/img/icon_person.svg")}
|
||||
className="mx_RoomTile_dm"
|
||||
|
||||
@@ -32,7 +32,7 @@ export default class VerifySessionToast extends React.PureComponent {
|
||||
DeviceListener.sharedInstance().dismissVerification(this.props.deviceId);
|
||||
};
|
||||
|
||||
_onVerifyClick = async () => {
|
||||
_onReviewClick = async () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
|
||||
@@ -47,10 +47,10 @@ export default class VerifySessionToast extends React.PureComponent {
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{_t("Other users may not trust it")}</div>
|
||||
<div className="mx_Toast_description">{_t("Review & verify your new session")}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Verify")} onClick={this._onVerifyClick} />
|
||||
<FormButton label={_t("Review")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
68
src/components/views/toasts/SetupEncryptionToast.js
Normal file
68
src/components/views/toasts/SetupEncryptionToast.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
export default class SetupEncryptionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf(['set_up_encryption', 'verify_this_session', 'upgrade_encryption']).isRequired,
|
||||
};
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissEncryptionSetup();
|
||||
};
|
||||
|
||||
_onSetupClick = async () => {
|
||||
accessSecretStorage();
|
||||
};
|
||||
|
||||
getDescription() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
case 'upgrade_encryption':
|
||||
return _t('Verify your other devices easier');
|
||||
case 'verify_this_session':
|
||||
return _t('Other users may not trust it');
|
||||
}
|
||||
}
|
||||
|
||||
getSetupCaption() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
case 'upgrade_encryption':
|
||||
return _t('Upgrade');
|
||||
case 'verify_this_session':
|
||||
return _t('Verify');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{this.getDescription()}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={this.getSetupCaption()} onClick={this._onSetupClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,13 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||
this.setState({counter});
|
||||
}, 1000);
|
||||
request.on("change", this._checkRequestIsPending);
|
||||
// We should probably have a separate class managing the active verification toasts,
|
||||
// rather than monitoring this in the toast component itself, since we'll get problems
|
||||
// like the toasdt not going away when the verification is cancelled unless it's the
|
||||
// one on the top (ie. the one that's mounted).
|
||||
// As a quick & dirty fix, check the toast is still relevant when it mounts (this prevents
|
||||
// a toast hanging around after logging in if you did a verification as part of login).
|
||||
this._checkRequestIsPending();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
Reference in New Issue
Block a user