Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/15187
Conflicts: src/settings/Settings.ts src/settings/UIFeature.ts
This commit is contained in:
commit
598c982f02
62 changed files with 744 additions and 644 deletions
|
@ -149,7 +149,6 @@
|
||||||
"eslint-plugin-flowtype": "^2.50.3",
|
"eslint-plugin-flowtype": "^2.50.3",
|
||||||
"eslint-plugin-react": "^7.20.3",
|
"eslint-plugin-react": "^7.20.3",
|
||||||
"eslint-plugin-react-hooks": "^2.5.1",
|
"eslint-plugin-react-hooks": "^2.5.1",
|
||||||
"file-loader": "^3.0.1",
|
|
||||||
"glob": "^5.0.15",
|
"glob": "^5.0.15",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"jest-canvas-mock": "^2.2.0",
|
"jest-canvas-mock": "^2.2.0",
|
||||||
|
@ -158,7 +157,6 @@
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
"react-test-renderer": "^16.13.1",
|
"react-test-renderer": "^16.13.1",
|
||||||
"rimraf": "^2.7.1",
|
"rimraf": "^2.7.1",
|
||||||
"source-map-loader": "^0.2.4",
|
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-standard": "^18.3.0",
|
"stylelint-config-standard": "^18.3.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
|
|
|
@ -91,11 +91,12 @@
|
||||||
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||||
@import "./views/dialogs/_UserSettingsDialog.scss";
|
@import "./views/dialogs/_UserSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
|
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
@import "./views/dialogs/security/_AccessSecretStorageDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
|
@import "./views/dialogs/security/_CreateCrossSigningDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
@import "./views/dialogs/security/_CreateKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss";
|
@import "./views/dialogs/security/_CreateSecretStorageDialog.scss";
|
||||||
@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss";
|
@import "./views/dialogs/security/_KeyBackupFailedDialog.scss";
|
||||||
|
@import "./views/dialogs/security/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/directory/_NetworkDropdown.scss";
|
@import "./views/directory/_NetworkDropdown.scss";
|
||||||
@import "./views/elements/_AccessibleButton.scss";
|
@import "./views/elements/_AccessibleButton.scss";
|
||||||
@import "./views/elements/_AddressSelector.scss";
|
@import "./views/elements/_AddressSelector.scss";
|
||||||
|
@ -187,7 +188,6 @@
|
||||||
@import "./views/rooms/_RoomHeader.scss";
|
@import "./views/rooms/_RoomHeader.scss";
|
||||||
@import "./views/rooms/_RoomList.scss";
|
@import "./views/rooms/_RoomList.scss";
|
||||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
|
||||||
@import "./views/rooms/_RoomSublist.scss";
|
@import "./views/rooms/_RoomSublist.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
|
|
|
@ -80,6 +80,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_Toast_icon_secure_backup::after {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/secure-backup.svg');
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Toast_title, .mx_Toast_body {
|
.mx_Toast_title, .mx_Toast_body {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&.mx_WelcomePage_registrationDisabled {
|
||||||
|
.mx_ButtonCreateAccount {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Welcome .mx_AuthBody_language {
|
.mx_Welcome .mx_AuthBody_language {
|
||||||
|
|
|
@ -71,9 +71,12 @@ limitations under the License.
|
||||||
margin-right: 64px;
|
margin-right: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container {
|
||||||
|
width: 299px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ShareDialog_social_container {
|
.mx_ShareDialog_social_container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 299px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ShareDialog_social_icon {
|
.mx_ShareDialog_social_icon {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_CreateCrossSigningDialog {
|
||||||
|
// Why you ask? Because CompleteSecurityBody is 600px so this is the width
|
||||||
|
// we end up when in there, but when in our own dialog we set our own width
|
||||||
|
// so need to fix it to something sensible as otherwise we'd end up either
|
||||||
|
// really wide or really narrow depending on the phase. I bet you wish you
|
||||||
|
// never asked.
|
||||||
|
width: 560px;
|
||||||
|
|
||||||
|
details .mx_AccessibleButton {
|
||||||
|
margin: 1em 0; // emulate paragraph spacing because we can't put this button in a paragraph due to HTML rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCrossSigningDialog .mx_Dialog_title {
|
||||||
|
/* TODO: Consider setting this for all dialog titles. */
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
background-color: $room-warning-bg-color;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid $primary-hairline-color;
|
|
||||||
border-bottom: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_header {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_body {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_secondary {
|
|
||||||
font-size: 90%;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
|
@ -28,4 +28,8 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_CrossSigningPanel_buttonRow {
|
.mx_CrossSigningPanel_buttonRow {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
|
||||||
|
:nth-child(n + 1) {
|
||||||
|
margin-inline-end: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,10 @@ import {
|
||||||
hideToast as hideUnverifiedSessionsToast,
|
hideToast as hideUnverifiedSessionsToast,
|
||||||
showToast as showUnverifiedSessionsToast,
|
showToast as showUnverifiedSessionsToast,
|
||||||
} from "./toasts/UnverifiedSessionToast";
|
} from "./toasts/UnverifiedSessionToast";
|
||||||
import { privateShouldBeEncrypted } from "./createRoom";
|
|
||||||
import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager";
|
import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager";
|
||||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
import { isLoggedIn } from './components/structures/MatrixChat';
|
import { isLoggedIn } from './components/structures/MatrixChat';
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -66,6 +65,7 @@ export default class DeviceListener {
|
||||||
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
||||||
MatrixClientPeg.get().on('sync', this._onSync);
|
MatrixClientPeg.get().on('sync', this._onSync);
|
||||||
|
MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
|
||||||
this.dispatcherRef = dis.register(this._onAction);
|
this.dispatcherRef = dis.register(this._onAction);
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ export default class DeviceListener {
|
||||||
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||||
|
MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
|
||||||
}
|
}
|
||||||
if (this.dispatcherRef) {
|
if (this.dispatcherRef) {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
@ -169,6 +170,16 @@ export default class DeviceListener {
|
||||||
if (state === 'PREPARED' && prevState === null) this._recheck();
|
if (state === 'PREPARED' && prevState === null) this._recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onRoomStateEvents = (ev: MatrixEvent) => {
|
||||||
|
if (ev.getType() !== "m.room.encryption") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a room changes to encrypted, re-check as it may be our first
|
||||||
|
// encrypted room. This also catches encrypted room creation as well.
|
||||||
|
this._recheck();
|
||||||
|
};
|
||||||
|
|
||||||
_onAction = ({ action }) => {
|
_onAction = ({ action }) => {
|
||||||
if (action !== "on_logged_in") return;
|
if (action !== "on_logged_in") return;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
|
@ -189,9 +200,7 @@ export default class DeviceListener {
|
||||||
// If we're in the middle of a secret storage operation, we're likely
|
// If we're in the middle of a secret storage operation, we're likely
|
||||||
// modifying the state involved here, so don't add new toasts to setup.
|
// modifying the state involved here, so don't add new toasts to setup.
|
||||||
if (isSecretStorageBeingAccessed()) return false;
|
if (isSecretStorageBeingAccessed()) return false;
|
||||||
// In a default configuration, show the toasts. If the well-known config causes e2ee default to be false
|
// Show setup toasts once the user is in at least one encrypted room.
|
||||||
// then do not show the toasts until user is in at least one encrypted room.
|
|
||||||
if (privateShouldBeEncrypted()) return true;
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
||||||
}
|
}
|
||||||
|
@ -207,8 +216,6 @@ export default class DeviceListener {
|
||||||
// (we add a listener on sync to do once check after the initial sync is done)
|
// (we add a listener on sync to do once check after the initial sync is done)
|
||||||
if (!cli.isInitialSyncComplete()) return;
|
if (!cli.isInitialSyncComplete()) return;
|
||||||
|
|
||||||
// JRS: This will change again in the next PR which moves secret storage
|
|
||||||
// later in the process.
|
|
||||||
const crossSigningReady = await cli.isCrossSigningReady();
|
const crossSigningReady = await cli.isCrossSigningReady();
|
||||||
const secretStorageReady = await cli.isSecretStorageReady();
|
const secretStorageReady = await cli.isSecretStorageReady();
|
||||||
const allSystemsReady = crossSigningReady && secretStorageReady;
|
const allSystemsReady = crossSigningReady && secretStorageReady;
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
|
import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog';
|
||||||
|
import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog';
|
||||||
|
|
||||||
// This stores the secret storage private keys in memory for the JS SDK. This is
|
// This stores the secret storage private keys in memory for the JS SDK. This is
|
||||||
// only meant to act as a cache to avoid prompting the user multiple times
|
// only meant to act as a cache to avoid prompting the user multiple times
|
||||||
|
@ -87,8 +89,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
return decodeRecoveryKey(recoveryKey);
|
return decodeRecoveryKey(recoveryKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const AccessSecretStorageDialog =
|
|
||||||
sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
|
|
||||||
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
||||||
AccessSecretStorageDialog,
|
AccessSecretStorageDialog,
|
||||||
/* props= */
|
/* props= */
|
||||||
|
@ -181,7 +181,6 @@ export const crossSigningCallbacks = {
|
||||||
export async function promptForBackupPassphrase() {
|
export async function promptForBackupPassphrase() {
|
||||||
let key;
|
let key;
|
||||||
|
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
showSummary: false, keyCallback: k => key = k,
|
showSummary: false, keyCallback: k => key = k,
|
||||||
}, null, /* priority = */ false, /* static = */ true);
|
}, null, /* priority = */ false, /* static = */ true);
|
||||||
|
@ -221,7 +220,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
// This dialog calls bootstrap itself after guiding the user through
|
// This dialog calls bootstrap itself after guiding the user through
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
import("./async-components/views/dialogs/security/CreateSecretStorageDialog"),
|
||||||
{
|
{
|
||||||
forceReset,
|
forceReset,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
export default class IgnoreRecoveryReminderDialog extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
onDontAskAgain: PropTypes.func.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
onSetup: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
onDontAskAgainClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
this.props.onDontAskAgain();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetupClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
this.props.onSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
|
||||||
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_IgnoreRecoveryReminderDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={_t("Are you sure?")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p>{_t(
|
|
||||||
"Without setting up Secure Message Recovery, " +
|
|
||||||
"you'll lose your secure message history when you " +
|
|
||||||
"log out.",
|
|
||||||
)}</p>
|
|
||||||
<p>{_t(
|
|
||||||
"If you don't want to set this up now, you can later " +
|
|
||||||
"in Settings.",
|
|
||||||
)}</p>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<DialogButtons
|
|
||||||
primaryButton={_t("Set up")}
|
|
||||||
onPrimaryButtonClick={this.onSetupClick}
|
|
||||||
cancelButton={_t("Don't ask again")}
|
|
||||||
onCancel={this.onDontAskAgainClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,6 +30,7 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
||||||
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
const PHASE_LOADING = 0;
|
||||||
|
@ -280,21 +281,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
const { forceReset } = this.props;
|
const { forceReset } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// JRS: In an upcoming change, the cross-signing steps will be
|
|
||||||
// removed from here and this will instead be about secret storage
|
|
||||||
// only.
|
|
||||||
if (forceReset) {
|
if (forceReset) {
|
||||||
console.log("Forcing cross-signing and secret storage reset");
|
console.log("Forcing secret storage reset");
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this._recoveryKey,
|
||||||
setupNewKeyBackup: true,
|
setupNewKeyBackup: true,
|
||||||
setupNewSecretStorage: true,
|
setupNewSecretStorage: true,
|
||||||
});
|
});
|
||||||
await cli.bootstrapCrossSigning({
|
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
|
||||||
setupNewCrossSigning: true,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
// For password authentication users after 2020-09, this cross-signing
|
||||||
|
// step will be a no-op since it is now setup during registration or login
|
||||||
|
// when needed. We should keep this here to cover other cases such as:
|
||||||
|
// * Users with existing sessions prior to 2020-09 changes
|
||||||
|
// * SSO authentication users which require interactive auth to upload
|
||||||
|
// keys (and also happen to skip all post-authentication flows at the
|
||||||
|
// moment via token login)
|
||||||
await cli.bootstrapCrossSigning({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||||
});
|
});
|
||||||
|
@ -341,7 +342,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
// so let's stash it here, rather than prompting for it twice.
|
// so let's stash it here, rather than prompting for it twice.
|
||||||
const keyCallback = k => this._backupKey = k;
|
const keyCallback = k => this._backupKey = k;
|
||||||
|
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog,
|
'Restore Backup', '', RestoreKeyBackupDialog,
|
||||||
{
|
{
|
|
@ -17,11 +17,11 @@ limitations under the License.
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../../index';
|
||||||
|
|
||||||
const PHASE_EDIT = 1;
|
const PHASE_EDIT = 1;
|
||||||
const PHASE_EXPORTING = 2;
|
const PHASE_EXPORTING = 2;
|
|
@ -18,9 +18,9 @@ import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
function readFileAsArrayBuffer(file) {
|
function readFileAsArrayBuffer(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
|
@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import dis from "../../../../dispatcher/dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import {Action} from "../../../../dispatcher/actions";
|
import {Action} from "../../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
|
@ -41,7 +42,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupClick = async () => {
|
onSetupClick = async () => {
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, {
|
'Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
onFinished: this.props.onFinished,
|
onFinished: this.props.onFinished,
|
|
@ -1501,12 +1501,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (haveNewVersion) {
|
if (haveNewVersion) {
|
||||||
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
||||||
import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'),
|
import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'),
|
||||||
{ newVersionInfo },
|
{ newVersionInfo },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
||||||
import('../../async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog'),
|
import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1881,6 +1881,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return this.props.makeRegistrationUrl(params);
|
return this.props.makeRegistrationUrl(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After registration or login, we run various post-auth steps before entering the app
|
||||||
|
* proper, such setting up cross-signing or verifying the new session.
|
||||||
|
*
|
||||||
|
* Note: SSO users (and any others using token login) currently do not pass through
|
||||||
|
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||||
|
*/
|
||||||
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
|
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
|
||||||
this.accountPassword = password;
|
this.accountPassword = password;
|
||||||
// self-destruct the password after 5mins
|
// self-destruct the password after 5mins
|
||||||
|
@ -1947,7 +1954,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
||||||
let view;
|
let view = null;
|
||||||
|
|
||||||
if (this.state.view === Views.LOADING) {
|
if (this.state.view === Views.LOADING) {
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
@ -2026,7 +2033,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
} else if (this.state.view === Views.WELCOME) {
|
} else if (this.state.view === Views.WELCOME) {
|
||||||
const Welcome = sdk.getComponent('auth.Welcome');
|
const Welcome = sdk.getComponent('auth.Welcome');
|
||||||
view = <Welcome />;
|
view = <Welcome />;
|
||||||
} else if (this.state.view === Views.REGISTER) {
|
} else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||||
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
||||||
view = (
|
view = (
|
||||||
|
@ -2044,7 +2051,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
{...this.getServerProperties()}
|
{...this.getServerProperties()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.FORGOT_PASSWORD) {
|
} else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) {
|
||||||
const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
|
const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
|
||||||
view = (
|
view = (
|
||||||
<ForgotPassword
|
<ForgotPassword
|
||||||
|
@ -2055,6 +2062,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.LOGIN) {
|
} else if (this.state.view === Views.LOGIN) {
|
||||||
|
const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset);
|
||||||
const Login = sdk.getComponent('structures.auth.Login');
|
const Login = sdk.getComponent('structures.auth.Login');
|
||||||
view = (
|
view = (
|
||||||
<Login
|
<Login
|
||||||
|
@ -2063,7 +2071,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
fallbackHsUrl={this.getFallbackHsUrl()}
|
fallbackHsUrl={this.getFallbackHsUrl()}
|
||||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
|
||||||
onServerConfigChange={this.onServerConfigChange}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
fragmentAfterLogin={fragmentAfterLogin}
|
fragmentAfterLogin={fragmentAfterLogin}
|
||||||
{...this.getServerProperties()}
|
{...this.getServerProperties()}
|
||||||
|
|
|
@ -135,6 +135,9 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// whether to use the irc layout
|
// whether to use the irc layout
|
||||||
useIRCLayout: PropTypes.bool,
|
useIRCLayout: PropTypes.bool,
|
||||||
|
|
||||||
|
// whether or not to show flair at all
|
||||||
|
enableFlair: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force props to be loaded for useIRCLayout
|
// Force props to be loaded for useIRCLayout
|
||||||
|
@ -579,7 +582,8 @@ export default class MessagePanel extends React.Component {
|
||||||
data-scroll-tokens={scrollToken}
|
data-scroll-tokens={scrollToken}
|
||||||
>
|
>
|
||||||
<TileErrorBoundary mxEvent={mxEv}>
|
<TileErrorBoundary mxEvent={mxEv}>
|
||||||
<EventTile mxEvent={mxEv}
|
<EventTile
|
||||||
|
mxEvent={mxEv}
|
||||||
continuation={continuation}
|
continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
|
@ -598,6 +602,7 @@ export default class MessagePanel extends React.Component {
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
useIRCLayout={this.props.useIRCLayout}
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={this.props.enableFlair}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>
|
</TileErrorBoundary>
|
||||||
</li>,
|
</li>,
|
||||||
|
|
|
@ -65,7 +65,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar";
|
||||||
import ForwardMessage from "../views/rooms/ForwardMessage";
|
import ForwardMessage from "../views/rooms/ForwardMessage";
|
||||||
import SearchBar from "../views/rooms/SearchBar";
|
import SearchBar from "../views/rooms/SearchBar";
|
||||||
import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
|
import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
|
||||||
import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder";
|
|
||||||
import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel";
|
import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel";
|
||||||
import AuxPanel from "../views/rooms/AuxPanel";
|
import AuxPanel from "../views/rooms/AuxPanel";
|
||||||
import RoomHeader from "../views/rooms/RoomHeader";
|
import RoomHeader from "../views/rooms/RoomHeader";
|
||||||
|
@ -816,12 +815,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomRecoveryReminderDontAskAgain = () => {
|
|
||||||
// Called when the option to not ask again is set:
|
|
||||||
// force an update to hide the recovery reminder
|
|
||||||
this.forceUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
private onKeyBackupStatus = () => {
|
private onKeyBackupStatus = () => {
|
||||||
// Key backup status changes affect whether the in-room recovery
|
// Key backup status changes affect whether the in-room recovery
|
||||||
// reminder is displayed.
|
// reminder is displayed.
|
||||||
|
@ -1858,13 +1851,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.state.room.userMayUpgradeRoom(this.context.credentials.userId)
|
this.state.room.userMayUpgradeRoom(this.context.credentials.userId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const showRoomRecoveryReminder = (
|
|
||||||
this.context.isCryptoEnabled() &&
|
|
||||||
SettingsStore.getValue("showRoomRecoveryReminder") &&
|
|
||||||
this.context.isRoomEncrypted(this.state.room.roomId) &&
|
|
||||||
this.context.getKeyBackupEnabled() === false
|
|
||||||
);
|
|
||||||
|
|
||||||
const hiddenHighlightCount = this.getHiddenHighlightCount();
|
const hiddenHighlightCount = this.getHiddenHighlightCount();
|
||||||
|
|
||||||
let aux = null;
|
let aux = null;
|
||||||
|
@ -1883,9 +1869,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
} else if (showRoomUpgradeBar) {
|
} else if (showRoomUpgradeBar) {
|
||||||
aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
|
aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
|
||||||
hideCancel = true;
|
hideCancel = true;
|
||||||
} else if (showRoomRecoveryReminder) {
|
|
||||||
aux = <RoomRecoveryReminder onDontAskAgainSet={this.onRoomRecoveryReminderDontAskAgain} />;
|
|
||||||
hideCancel = true;
|
|
||||||
} else if (this.state.showingPinned) {
|
} else if (this.state.showingPinned) {
|
||||||
hideCancel = true; // has own cancel
|
hideCancel = true; // has own cancel
|
||||||
aux = <PinnedEventsPanel room={this.state.room} onCancelClick={this.onPinnedClick} />;
|
aux = <PinnedEventsPanel room={this.state.room} onCancelClick={this.onPinnedClick} />;
|
||||||
|
|
|
@ -35,6 +35,7 @@ import Timer from '../../utils/Timer';
|
||||||
import shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -1446,6 +1447,7 @@ class TimelinePanel extends React.Component {
|
||||||
editState={this.state.editState}
|
editState={this.state.editState}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
useIRCLayout={this.props.useIRCLayout}
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import AsyncWrapper from '../../../AsyncWrapper';
|
import AuthPage from '../../views/auth/AuthPage';
|
||||||
import * as sdk from '../../../index';
|
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||||
|
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||||
|
|
||||||
export default class E2eSetup extends React.Component {
|
export default class E2eSetup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -25,21 +26,11 @@ export default class E2eSetup extends React.Component {
|
||||||
accountPassword: PropTypes.string,
|
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() {
|
render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
|
||||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<CompleteSecurityBody>
|
<CompleteSecurityBody>
|
||||||
<AsyncWrapper prom={this._createStorageDialogPromise}
|
<CreateCrossSigningDialog
|
||||||
hasCancel={false}
|
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
accountPassword={this.props.accountPassword}
|
accountPassword={this.props.accountPassword}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,6 +28,8 @@ import classNames from "classnames";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import SSOButton from "../../views/elements/SSOButton";
|
import SSOButton from "../../views/elements/SSOButton";
|
||||||
import PlatformPeg from '../../../PlatformPeg';
|
import PlatformPeg from '../../../PlatformPeg';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// For validating phone numbers without country codes
|
// For validating phone numbers without country codes
|
||||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||||
|
@ -679,7 +681,7 @@ export default class LoginComponent extends React.Component {
|
||||||
{_t("If you've joined lots of rooms, this might take a while")}
|
{_t("If you've joined lots of rooms, this might take a while")}
|
||||||
</div> }
|
</div> }
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else if (SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
footer = (
|
footer = (
|
||||||
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
|
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
|
||||||
{ _t('Create account') }
|
{ _t('Create account') }
|
||||||
|
|
|
@ -15,10 +15,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import AuthPage from "./AuthPage";
|
import AuthPage from "./AuthPage";
|
||||||
import {_td} from "../../../languageHandler";
|
import {_td} from "../../../languageHandler";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// translatable strings for Welcome pages
|
// translatable strings for Welcome pages
|
||||||
_td("Sign in with SSO");
|
_td("Sign in with SSO");
|
||||||
|
@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<div className="mx_Welcome">
|
<div className={classNames("mx_Welcome", {
|
||||||
|
mx_WelcomePage_registrationDisabled: !SettingsStore.getValue(UIFeature.Registration),
|
||||||
|
})}>
|
||||||
<EmbeddedPage
|
<EmbeddedPage
|
||||||
className="mx_WelcomePage"
|
className="mx_WelcomePage"
|
||||||
url={pageUrl}
|
url={pageUrl}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions";
|
||||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
if (this.state.filterText.startsWith('@')) {
|
if (this.state.filterText.startsWith('@')) {
|
||||||
// Assume mxid
|
// Assume mxid
|
||||||
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
|
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
|
||||||
} else {
|
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Assume email
|
// Assume email
|
||||||
newMember = new ThreepidMember(this.state.filterText);
|
newMember = new ThreepidMember(this.state.filterText);
|
||||||
}
|
}
|
||||||
|
@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
this.setState({tryingIdentityServer: true});
|
this.setState({tryingIdentityServer: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (term.indexOf('@') > 0 && Email.looksValid(term)) {
|
if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Start off by suggesting the plain email while we try and resolve it
|
// Start off by suggesting the plain email while we try and resolve it
|
||||||
// to a real account.
|
// to a real account.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderIdentityServerWarning() {
|
_renderIdentityServerWarning() {
|
||||||
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) {
|
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
|
||||||
|
!SettingsStore.getValue(UIFeature.IdentityServer)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1086,22 +1090,38 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
let buttonText;
|
let buttonText;
|
||||||
let goButtonFn;
|
let goButtonFn;
|
||||||
|
|
||||||
|
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
|
||||||
|
|
||||||
const userId = MatrixClientPeg.get().getUserId();
|
const userId = MatrixClientPeg.get().getUserId();
|
||||||
if (this.props.kind === KIND_DM) {
|
if (this.props.kind === KIND_DM) {
|
||||||
title = _t("Direct Messages");
|
title = _t("Direct Messages");
|
||||||
|
|
||||||
|
if (identityServersEnabled) {
|
||||||
helpText = _t(
|
helpText = _t(
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
||||||
{},
|
{},
|
||||||
{userId: () => {
|
{userId: () => {
|
||||||
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>;
|
return (
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||||
|
);
|
||||||
}},
|
}},
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
helpText = _t(
|
||||||
|
"Start a conversation with someone using their name or username (like <userId/>).",
|
||||||
|
{},
|
||||||
|
{userId: () => {
|
||||||
|
return (
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||||
|
);
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||||
helpText = _t(
|
const inviteText = _t("This won't invite them to %(communityName)s. " +
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address. " +
|
"To invite someone to %(communityName)s, click <a>here</a>",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
|
|
||||||
"<a>here</a>.",
|
|
||||||
{communityName}, {
|
{communityName}, {
|
||||||
userId: () => {
|
userId: () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1122,13 +1142,19 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
helpText = <React.Fragment>
|
||||||
|
{ helpText } {inviteText}
|
||||||
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
buttonText = _t("Go");
|
buttonText = _t("Go");
|
||||||
goButtonFn = this._startDm;
|
goButtonFn = this._startDm;
|
||||||
} else { // KIND_INVITE
|
} else { // KIND_INVITE
|
||||||
title = _t("Invite to this room");
|
title = _t("Invite to this room");
|
||||||
|
|
||||||
|
if (identityServersEnabled) {
|
||||||
helpText = _t(
|
helpText = _t(
|
||||||
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
|
"Invite someone using their name, username (like <userId/>), email address or " +
|
||||||
|
"<a>share this room</a>.",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
userId: () =>
|
userId: () =>
|
||||||
|
@ -1137,6 +1163,19 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
helpText = _t(
|
||||||
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
userId: () =>
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||||
|
a: (sub) =>
|
||||||
|
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buttonText = _t("Invite");
|
buttonText = _t("Invite");
|
||||||
goButtonFn = this._inviteUsers;
|
goButtonFn = this._inviteUsers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
|
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
||||||
|
|
||||||
export default class LogoutDialog extends React.Component {
|
export default class LogoutDialog extends React.Component {
|
||||||
defaultProps = {
|
defaultProps = {
|
||||||
|
@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component {
|
||||||
|
|
||||||
_onExportE2eKeysClicked() {
|
_onExportE2eKeysClicked() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
|
@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component {
|
||||||
// A key backup exists for this account, but the creating device is not
|
// A key backup exists for this account, but the creating device is not
|
||||||
// verified, so restore the backup which will give us the keys from it and
|
// verified, so restore the backup which will give us the keys from it and
|
||||||
// allow us to trust it (ie. upload keys to it)
|
// allow us to trust it (ie. upload keys to it)
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
||||||
/* priority = */ false, /* static = */ true,
|
/* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"),
|
||||||
null, null, /* priority = */ false, /* static = */ true,
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings";
|
||||||
import StyledCheckbox from '../elements/StyledCheckbox';
|
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||||
import { IDialogProps } from "./IDialogProps";
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
const socials = [
|
const socials = [
|
||||||
{
|
{
|
||||||
|
@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||||
const matrixToUrl = this.getUrl();
|
const matrixToUrl = this.getUrl();
|
||||||
const encodedUrl = encodeURIComponent(matrixToUrl);
|
const encodedUrl = encodeURIComponent(matrixToUrl);
|
||||||
|
|
||||||
|
const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode);
|
||||||
|
const showSocials = SettingsStore.getValue(UIFeature.ShareSocial);
|
||||||
|
|
||||||
|
let qrSocialSection;
|
||||||
|
if (showQrCode || showSocials) {
|
||||||
|
qrSocialSection = <>
|
||||||
|
<hr />
|
||||||
|
<div className="mx_ShareDialog_split">
|
||||||
|
{ showQrCode && <div className="mx_ShareDialog_qrcode_container">
|
||||||
|
<QRCode data={matrixToUrl} width={256} />
|
||||||
|
</div> }
|
||||||
|
{ showSocials && <div className="mx_ShareDialog_social_container">
|
||||||
|
{ socials.map((social) => (
|
||||||
|
<a
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
key={social.name}
|
||||||
|
title={social.name}
|
||||||
|
href={social.url(encodedUrl)}
|
||||||
|
className="mx_ShareDialog_social_icon"
|
||||||
|
>
|
||||||
|
<img src={social.img} alt={social.name} height={64} width={64} />
|
||||||
|
</a>
|
||||||
|
)) }
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return <BaseDialog
|
return <BaseDialog
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -220,27 +251,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{ checkbox }
|
{ checkbox }
|
||||||
<hr />
|
{ qrSocialSection }
|
||||||
|
|
||||||
<div className="mx_ShareDialog_split">
|
|
||||||
<div className="mx_ShareDialog_qrcode_container">
|
|
||||||
<QRCode data={matrixToUrl} width={256} />
|
|
||||||
</div>
|
|
||||||
<div className="mx_ShareDialog_social_container">
|
|
||||||
{ socials.map((social) => (
|
|
||||||
<a
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
target="_blank"
|
|
||||||
key={social.name}
|
|
||||||
title={social.name}
|
|
||||||
href={social.url(encodedUrl)}
|
|
||||||
className="mx_ShareDialog_social_icon"
|
|
||||||
>
|
|
||||||
<img src={social.img} alt={social.name} height={64} width={64} />
|
|
||||||
</a>
|
|
||||||
)) }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,12 +87,14 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
"mx_UserSettingsDialog_appearanceIcon",
|
"mx_UserSettingsDialog_appearanceIcon",
|
||||||
<AppearanceUserSettingsTab />,
|
<AppearanceUserSettingsTab />,
|
||||||
));
|
));
|
||||||
|
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_FLAIR_TAB,
|
USER_FLAIR_TAB,
|
||||||
_td("Flair"),
|
_td("Flair"),
|
||||||
"mx_UserSettingsDialog_flairIcon",
|
"mx_UserSettingsDialog_flairIcon",
|
||||||
<FlairUserSettingsTab />,
|
<FlairUserSettingsTab />,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_NOTIFICATIONS_TAB,
|
USER_NOTIFICATIONS_TAB,
|
||||||
_td("Notifications"),
|
_td("Notifications"),
|
||||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../../languageHandler";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../../index";
|
||||||
|
|
||||||
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
|
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.
|
||||||
|
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 { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
|
import { _t } from '../../../../languageHandler';
|
||||||
|
import Modal from '../../../../Modal';
|
||||||
|
import { SSOAuthEntry } from '../../auth/InteractiveAuthEntryComponents';
|
||||||
|
import DialogButtons from '../../elements/DialogButtons';
|
||||||
|
import BaseDialog from '../BaseDialog';
|
||||||
|
import Spinner from '../../elements/Spinner';
|
||||||
|
import InteractiveAuthDialog from '../InteractiveAuthDialog';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walks the user through the process of creating a cross-signing keys. In most
|
||||||
|
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
||||||
|
* may need to complete some steps to proceed.
|
||||||
|
*/
|
||||||
|
export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
accountPassword: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
error: null,
|
||||||
|
// Does the server offer a UI auth flow with just m.login.password
|
||||||
|
// for /keys/device_signing/upload?
|
||||||
|
canUploadKeysWithPasswordOnly: null,
|
||||||
|
accountPassword: props.accountPassword || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.accountPassword) {
|
||||||
|
// If we have an account password in memory, let's simplify and
|
||||||
|
// assume it means password auth is also supported for device
|
||||||
|
// signing key upload as well. This avoids hitting the server to
|
||||||
|
// test auth flows, which may be slow under high load.
|
||||||
|
this.state.canUploadKeysWithPasswordOnly = true;
|
||||||
|
} else {
|
||||||
|
this._queryKeyUploadAuth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._bootstrapCrossSigning();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _queryKeyUploadAuth() {
|
||||||
|
try {
|
||||||
|
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
||||||
|
// We should never get here: the server should always require
|
||||||
|
// UI auth to upload device signing keys. If we do, we upload
|
||||||
|
// no keys which would be a no-op.
|
||||||
|
console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
|
||||||
|
} catch (error) {
|
||||||
|
if (!error.data || !error.data.flows) {
|
||||||
|
console.log("uploadDeviceSigningKeys advertised no flows!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canUploadKeysWithPasswordOnly = error.data.flows.some(f => {
|
||||||
|
return f.stages.length === 1 && f.stages[0] === 'm.login.password';
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
canUploadKeysWithPasswordOnly,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_doBootstrapUIAuth = async (makeRequest) => {
|
||||||
|
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
||||||
|
await makeRequest({
|
||||||
|
type: 'm.login.password',
|
||||||
|
identifier: {
|
||||||
|
type: 'm.id.user',
|
||||||
|
user: MatrixClientPeg.get().getUserId(),
|
||||||
|
},
|
||||||
|
// TODO: Remove `user` once servers support proper UIA
|
||||||
|
// See https://github.com/matrix-org/synapse/issues/5665
|
||||||
|
user: MatrixClientPeg.get().getUserId(),
|
||||||
|
password: this.state.accountPassword,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const dialogAesthetics = {
|
||||||
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
body: _t("To continue, use Single Sign On to prove your identity."),
|
||||||
|
continueText: _t("Single Sign On"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||||
|
title: _t("Confirm encryption setup"),
|
||||||
|
body: _t("Click the button below to confirm setting up encryption."),
|
||||||
|
continueText: _t("Confirm"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { finished } = Modal.createTrackedDialog(
|
||||||
|
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||||
|
{
|
||||||
|
title: _t("Setting up keys"),
|
||||||
|
matrixClient: MatrixClientPeg.get(),
|
||||||
|
makeRequest,
|
||||||
|
aestheticsForStagePhases: {
|
||||||
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const [confirmed] = await finished;
|
||||||
|
if (!confirmed) {
|
||||||
|
throw new Error("Cross-signing key upload auth canceled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_bootstrapCrossSigning = async () => {
|
||||||
|
this.setState({
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cli.bootstrapCrossSigning({
|
||||||
|
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||||
|
});
|
||||||
|
this.props.onFinished(true);
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({ error: e });
|
||||||
|
console.error("Error bootstrapping cross-signing", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCancel = () => {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content;
|
||||||
|
if (this.state.error) {
|
||||||
|
content = <div>
|
||||||
|
<p>{_t("Unable to set up keys")}</p>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
|
onPrimaryButtonClick={this._bootstrapCrossSigning}
|
||||||
|
onCancel={this._onCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
content = <div>
|
||||||
|
<Spinner />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className="mx_CreateCrossSigningDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("Setting up keys")}
|
||||||
|
hasCancel={false}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,16 +16,16 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody';
|
import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
|
||||||
import BaseDialog from './BaseDialog';
|
import BaseDialog from '../BaseDialog';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore';
|
import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore';
|
||||||
|
|
||||||
function iconFromPhase(phase) {
|
function iconFromPhase(phase) {
|
||||||
if (phase === PHASE_DONE) {
|
if (phase === PHASE_DONE) {
|
||||||
return require("../../../../res/img/e2e/verified.svg");
|
return require("../../../../../res/img/e2e/verified.svg");
|
||||||
} else {
|
} else {
|
||||||
return require("../../../../res/img/e2e/warning.svg");
|
return require("../../../../../res/img/e2e/warning.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
import * as Avatar from '../../../Avatar';
|
import * as Avatar from '../../../Avatar';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import EventTile from '../rooms/EventTile';
|
import EventTile from '../rooms/EventTile';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +123,11 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div className={className}>
|
return <div className={className}>
|
||||||
<EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} />
|
<EventTile
|
||||||
|
mxEvent={event}
|
||||||
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import escapeHtml from "escape-html";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||||
|
@ -366,6 +367,7 @@ export default class ReplyThread extends React.Component {
|
||||||
isRedacted={ev.isRedacted()}
|
isRedacted={ev.isRedacted()}
|
||||||
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
||||||
useIRCLayout={this.props.useIRCLayout}
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
/>
|
/>
|
||||||
</blockquote>;
|
</blockquote>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -206,6 +206,9 @@ export default class EventTile extends React.Component {
|
||||||
|
|
||||||
// whether to use the irc layout
|
// whether to use the irc layout
|
||||||
useIRCLayout: PropTypes.bool,
|
useIRCLayout: PropTypes.bool,
|
||||||
|
|
||||||
|
// whether or not to show flair at all
|
||||||
|
enableFlair: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -736,10 +739,10 @@ export default class EventTile extends React.Component {
|
||||||
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||||
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
enableFlair={!text}
|
enableFlair={this.props.enableFlair && !text}
|
||||||
text={text} />;
|
text={text} />;
|
||||||
} else {
|
} else {
|
||||||
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={this.props.enableFlair} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
function cancelQuoting() {
|
function cancelQuoting() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -80,11 +81,14 @@ export default class ReplyPreview extends React.Component {
|
||||||
onClick={cancelQuoting} />
|
onClick={cancelQuoting} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ReplyPreview_clear" />
|
<div className="mx_ReplyPreview_clear" />
|
||||||
<EventTile last={true}
|
<EventTile
|
||||||
|
last={true}
|
||||||
tileShape="reply_preview"
|
tileShape="reply_preview"
|
||||||
mxEvent={this.state.event}
|
mxEvent={this.state.event}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
|
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018, 2019 New Vector 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.
|
|
||||||
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 Modal from "../../../Modal";
|
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
|
||||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
|
||||||
|
|
||||||
export default class RoomRecoveryReminder extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
// called if the user sets the option to suppress this reminder in the future
|
|
||||||
onDontAskAgainSet: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
onDontAskAgainSet: function() {},
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
loading: true,
|
|
||||||
error: null,
|
|
||||||
backupInfo: null,
|
|
||||||
notNowClicked: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._loadBackupStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _loadBackupStatus() {
|
|
||||||
try {
|
|
||||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
|
||||||
backupInfo,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Unable to fetch key backup status", e);
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
|
||||||
error: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showSetupDialog = () => {
|
|
||||||
if (this.state.backupInfo) {
|
|
||||||
// A key backup exists for this account, but the creating device is not
|
|
||||||
// verified, so restore the backup which will give us the keys from it and
|
|
||||||
// allow us to trust it (ie. upload keys to it)
|
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
Modal.createTrackedDialog(
|
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
|
||||||
/* priority = */ false, /* static = */ true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
|
||||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
|
||||||
null, null, /* priority = */ false, /* static = */ true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onOnNotNowClick = () => {
|
|
||||||
this.setState({notNowClicked: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDontAskAgainClick = () => {
|
|
||||||
// When you choose "Don't ask again" from the room reminder, we show a
|
|
||||||
// dialog to confirm the choice.
|
|
||||||
Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder",
|
|
||||||
import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"),
|
|
||||||
{
|
|
||||||
onDontAskAgain: async () => {
|
|
||||||
await SettingsStore.setValue(
|
|
||||||
"showRoomRecoveryReminder",
|
|
||||||
null,
|
|
||||||
SettingLevel.ACCOUNT,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
this.props.onDontAskAgainSet();
|
|
||||||
},
|
|
||||||
onSetup: () => {
|
|
||||||
this.showSetupDialog();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetupClick = () => {
|
|
||||||
this.showSetupDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// If there was an error loading just don't display the banner: we'll try again
|
|
||||||
// next time the user switchs to the room.
|
|
||||||
if (this.state.error || this.state.loading || this.state.notNowClicked) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
|
||||||
|
|
||||||
let setupCaption;
|
|
||||||
if (this.state.backupInfo) {
|
|
||||||
setupCaption = _t("Connect this session to Key Backup");
|
|
||||||
} else {
|
|
||||||
setupCaption = _t("Start using Key Backup");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomRecoveryReminder">
|
|
||||||
<div className="mx_RoomRecoveryReminder_header">{_t(
|
|
||||||
"Never lose encrypted messages",
|
|
||||||
)}</div>
|
|
||||||
<div className="mx_RoomRecoveryReminder_body">
|
|
||||||
<p>{_t(
|
|
||||||
"Messages in this room are secured with end-to-end " +
|
|
||||||
"encryption. Only you and the recipient(s) have the " +
|
|
||||||
"keys to read these messages.",
|
|
||||||
)}</p>
|
|
||||||
<p>{_t(
|
|
||||||
"Securely back up your keys to avoid losing them. " +
|
|
||||||
"<a>Learn more.</a>", {},
|
|
||||||
{
|
|
||||||
// TODO: We don't have this link yet: this will prevent the translators
|
|
||||||
// having to re-translate the string when we do.
|
|
||||||
a: sub => '',
|
|
||||||
},
|
|
||||||
)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mx_RoomRecoveryReminder_buttons">
|
|
||||||
<AccessibleButton kind="primary"
|
|
||||||
onClick={this.onSetupClick}>
|
|
||||||
{setupCaption}
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
|
|
||||||
onClick={this.onOnNotNowClick}>
|
|
||||||
{ _t("Not now") }
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
|
|
||||||
onClick={this.onDontAskAgainClick}>
|
|
||||||
{ _t("Don't ask me again") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,6 +19,8 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {haveTileForEvent} from "./EventTile";
|
import {haveTileForEvent} from "./EventTile";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
export default class SearchResultTile extends React.Component {
|
export default class SearchResultTile extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -45,18 +47,27 @@ export default class SearchResultTile extends React.Component {
|
||||||
const ret = [<DateSeparator key={ts1 + "-search"} ts={ts1} />];
|
const ret = [<DateSeparator key={ts1 + "-search"} ts={ts1} />];
|
||||||
|
|
||||||
const timeline = result.context.getTimeline();
|
const timeline = result.context.getTimeline();
|
||||||
for (var j = 0; j < timeline.length; j++) {
|
for (let j = 0; j < timeline.length; j++) {
|
||||||
const ev = timeline[j];
|
const ev = timeline[j];
|
||||||
var highlights;
|
let highlights;
|
||||||
const contextual = (j != result.context.getOurEventIndex());
|
const contextual = (j != result.context.getOurEventIndex());
|
||||||
if (!contextual) {
|
if (!contextual) {
|
||||||
highlights = this.props.searchHighlights;
|
highlights = this.props.searchHighlights;
|
||||||
}
|
}
|
||||||
if (haveTileForEvent(ev)) {
|
if (haveTileForEvent(ev)) {
|
||||||
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
|
ret.push((
|
||||||
|
<EventTile
|
||||||
|
key={`${eventId}+${j}`}
|
||||||
|
mxEvent={ev}
|
||||||
|
contextual={contextual}
|
||||||
|
highlights={highlights}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
highlightLink={this.props.resultLink}
|
highlightLink={this.props.resultLink}
|
||||||
onHeightChanged={this.props.onHeightChanged} />);
|
onHeightChanged={this.props.onHeightChanged}
|
||||||
|
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -184,7 +184,7 @@ export default class ChangePassword extends React.Component {
|
||||||
|
|
||||||
_onExportE2eKeysClicked = () => {
|
_onExportE2eKeysClicked = () => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
|
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
|
||||||
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,7 @@ import * as sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import Spinner from '../elements/Spinner';
|
import Spinner from '../elements/Spinner';
|
||||||
import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
|
import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
|
||||||
|
import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog';
|
||||||
|
|
||||||
export default class CrossSigningPanel extends React.PureComponent {
|
export default class CrossSigningPanel extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -137,7 +138,6 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetCrossSigning = () => {
|
_resetCrossSigning = () => {
|
||||||
const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog");
|
|
||||||
Modal.createDialog(ConfirmDestroyCrossSigningDialog, {
|
Modal.createDialog(ConfirmDestroyCrossSigningDialog, {
|
||||||
onFinished: (act) => {
|
onFinished: (act) => {
|
||||||
if (!act) return;
|
if (!act) return;
|
||||||
|
@ -187,37 +187,46 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysExistAnywhere = (
|
const keysExistAnywhere = (
|
||||||
|
crossSigningPublicKeysOnDevice ||
|
||||||
crossSigningPrivateKeysInStorage ||
|
crossSigningPrivateKeysInStorage ||
|
||||||
crossSigningPublicKeysOnDevice
|
masterPrivateKeyCached ||
|
||||||
|
selfSigningPrivateKeyCached ||
|
||||||
|
userSigningPrivateKeyCached
|
||||||
);
|
);
|
||||||
const keysExistEverywhere = (
|
const keysExistEverywhere = (
|
||||||
|
crossSigningPublicKeysOnDevice &&
|
||||||
crossSigningPrivateKeysInStorage &&
|
crossSigningPrivateKeysInStorage &&
|
||||||
crossSigningPublicKeysOnDevice
|
masterPrivateKeyCached &&
|
||||||
|
selfSigningPrivateKeyCached &&
|
||||||
|
userSigningPrivateKeyCached
|
||||||
);
|
);
|
||||||
|
|
||||||
let resetButton;
|
const actions = [];
|
||||||
if (keysExistAnywhere) {
|
|
||||||
resetButton = (
|
// TODO: determine how better to expose this to users in addition to prompts at login/toast
|
||||||
<div className="mx_CrossSigningPanel_buttonRow">
|
if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
|
||||||
<AccessibleButton kind="danger" onClick={this._resetCrossSigning}>
|
actions.push(
|
||||||
{_t("Reset")}
|
<AccessibleButton key="setup" kind="primary" onClick={this._onBootstrapClick}>
|
||||||
</AccessibleButton>
|
{_t("Set up")}
|
||||||
</div>
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: determine how better to expose this to users in addition to prompts at login/toast
|
if (keysExistAnywhere) {
|
||||||
let bootstrapButton;
|
actions.push(
|
||||||
if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
|
<AccessibleButton key="reset" kind="danger" onClick={this._resetCrossSigning}>
|
||||||
bootstrapButton = (
|
{_t("Reset")}
|
||||||
<div className="mx_CrossSigningPanel_buttonRow">
|
</AccessibleButton>,
|
||||||
<AccessibleButton kind="primary" onClick={this._onBootstrapClick}>
|
|
||||||
{_t("Set up")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let actionRow;
|
||||||
|
if (actions.length) {
|
||||||
|
actionRow = <div className="mx_CrossSigningPanel_buttonRow">
|
||||||
|
{actions}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{summarisedStatus}
|
{summarisedStatus}
|
||||||
|
@ -230,7 +239,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Cross-signing private keys:")}</td>
|
<td>{_t("Cross-signing private keys:")}</td>
|
||||||
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")}</td>
|
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Master private key:")}</td>
|
<td>{_t("Master private key:")}</td>
|
||||||
|
@ -251,8 +260,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
</details>
|
</details>
|
||||||
{errorSection}
|
{errorSection}
|
||||||
{bootstrapButton}
|
{actionRow}
|
||||||
{resetButton}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { isSecureBackupRequired } from '../../../utils/WellKnownUtils';
|
||||||
import Spinner from '../elements/Spinner';
|
import Spinner from '../elements/Spinner';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import QuestionDialog from '../dialogs/QuestionDialog';
|
import QuestionDialog from '../dialogs/QuestionDialog';
|
||||||
import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog';
|
import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog';
|
||||||
import { accessSecretStorage } from '../../../SecurityManager';
|
import { accessSecretStorage } from '../../../SecurityManager';
|
||||||
|
|
||||||
export default class SecureBackupPanel extends React.PureComponent {
|
export default class SecureBackupPanel extends React.PureComponent {
|
||||||
|
@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const secretStorage = cli._crypto._secretStorage;
|
const secretStorage = cli._crypto._secretStorage;
|
||||||
|
|
||||||
const backupKeyStored = await cli.isKeyBackupKeyStored();
|
const backupKeyStored = !!(await cli.isKeyBackupKeyStored());
|
||||||
const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey();
|
const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey();
|
||||||
const backupKeyCached = !!(backupKeyFromCache);
|
const backupKeyCached = !!(backupKeyFromCache);
|
||||||
const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
|
const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
|
||||||
|
@ -150,7 +150,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
||||||
|
|
||||||
_startNewBackup = () => {
|
_startNewBackup = () => {
|
||||||
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
||||||
import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
|
import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'),
|
||||||
{
|
{
|
||||||
onFinished: () => {
|
onFinished: () => {
|
||||||
this._loadBackupStatus();
|
this._loadBackupStatus();
|
||||||
|
@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent {
|
||||||
</>;
|
</>;
|
||||||
|
|
||||||
actions.push(
|
actions.push(
|
||||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
<AccessibleButton key="restore" kind="primary" onClick={this._restoreBackup}>
|
||||||
{restoreButtonCaption}
|
{restoreButtonCaption}
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isSecureBackupRequired()) {
|
if (!isSecureBackupRequired()) {
|
||||||
actions.push(
|
actions.push(
|
||||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
<AccessibleButton key="delete" kind="danger" onClick={this._deleteBackup}>
|
||||||
{_t("Delete Backup")}
|
{_t("Delete Backup")}
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
|
@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
||||||
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||||
</>;
|
</>;
|
||||||
actions.push(
|
actions.push(
|
||||||
<AccessibleButton kind="primary" onClick={this._startNewBackup}>
|
<AccessibleButton key="setup" kind="primary" onClick={this._startNewBackup}>
|
||||||
{_t("Set up")}
|
{_t("Set up")}
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
|
@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
||||||
|
|
||||||
if (secretStorageKeyInAccount) {
|
if (secretStorageKeyInAccount) {
|
||||||
actions.push(
|
actions.push(
|
||||||
<AccessibleButton kind="danger" onClick={this._resetSecretStorage}>
|
<AccessibleButton key="reset" kind="danger" onClick={this._resetSecretStorage}>
|
||||||
{_t("Reset")}
|
{_t("Reset")}
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
|
|
|
@ -73,6 +73,18 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
urlPreviewSettings = null;
|
urlPreviewSettings = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let flairSection;
|
||||||
|
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||||
|
flairSection = <>
|
||||||
|
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
||||||
|
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||||
|
<RelatedGroupSettings roomId={room.roomId}
|
||||||
|
canSetRelatedGroups={canChangeGroups}
|
||||||
|
relatedGroupsEvent={groupsEvent} />
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
||||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||||
|
@ -87,13 +99,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
|
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_SettingsTab_heading">{_t("Other")}</div>
|
<div className="mx_SettingsTab_heading">{_t("Other")}</div>
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
{ flairSection }
|
||||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
|
||||||
<RelatedGroupSettings roomId={room.roomId}
|
|
||||||
canSetRelatedGroups={canChangeGroups}
|
|
||||||
relatedGroupsEvent={groupsEvent} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ urlPreviewSettings }
|
{ urlPreviewSettings }
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
|
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
|
||||||
|
|
|
@ -388,17 +388,31 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
width="18" height="18" alt={_t("Warning")} />
|
width="18" height="18" alt={_t("Warning")} />
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
let accountManagementSection;
|
||||||
|
if (SettingsStore.getValue(UIFeature.Deactivate)) {
|
||||||
|
accountManagementSection = <>
|
||||||
|
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div>
|
||||||
|
{this._renderManagementSection()}
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let discoverySection;
|
||||||
|
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
|
discoverySection = <>
|
||||||
|
<div className="mx_SettingsTab_heading">{discoWarning} {_t("Discovery")}</div>
|
||||||
|
{this._renderDiscoverySection()}
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab">
|
<div className="mx_SettingsTab">
|
||||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||||
{this._renderProfileSection()}
|
{this._renderProfileSection()}
|
||||||
{this._renderAccountSection()}
|
{this._renderAccountSection()}
|
||||||
{this._renderLanguageSection()}
|
{this._renderLanguageSection()}
|
||||||
<div className="mx_SettingsTab_heading">{discoWarning} {_t("Discovery")}</div>
|
{ discoverySection }
|
||||||
{this._renderDiscoverySection()}
|
|
||||||
{this._renderIntegrationManagerSection() /* Has its own title */}
|
{this._renderIntegrationManagerSection() /* Has its own title */}
|
||||||
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div>
|
{ accountManagementSection }
|
||||||
{this._renderManagementSection()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,14 +103,14 @@ export default class SecurityUserSettingsTab extends React.Component {
|
||||||
|
|
||||||
_onExportE2eKeysClicked = () => {
|
_onExportE2eKeysClicked = () => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||||
{matrixClient: MatrixClientPeg.get()},
|
{matrixClient: MatrixClientPeg.get()},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onImportE2eKeysClicked = () => {
|
_onImportE2eKeysClicked = () => {
|
||||||
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||||
import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
|
import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'),
|
||||||
{matrixClient: MatrixClientPeg.get()},
|
{matrixClient: MatrixClientPeg.get()},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -411,13 +411,12 @@
|
||||||
"Set password": "Set password",
|
"Set password": "Set password",
|
||||||
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
||||||
"Set Password": "Set Password",
|
"Set Password": "Set Password",
|
||||||
"Set up encryption": "Set up encryption",
|
"Set up Secure Backup": "Set up Secure Backup",
|
||||||
"Encryption upgrade available": "Encryption upgrade available",
|
"Encryption upgrade available": "Encryption upgrade available",
|
||||||
"Verify this session": "Verify this session",
|
"Verify this session": "Verify this session",
|
||||||
"Set up": "Set up",
|
|
||||||
"Upgrade": "Upgrade",
|
"Upgrade": "Upgrade",
|
||||||
"Verify": "Verify",
|
"Verify": "Verify",
|
||||||
"Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe",
|
"Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data",
|
||||||
"Other users may not trust it": "Other users may not trust it",
|
"Other users may not trust it": "Other users may not trust it",
|
||||||
"New login. Was this you?": "New login. Was this you?",
|
"New login. Was this you?": "New login. Was this you?",
|
||||||
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
|
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
|
||||||
|
@ -474,7 +473,6 @@
|
||||||
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
|
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
|
||||||
"Always show message timestamps": "Always show message timestamps",
|
"Always show message timestamps": "Always show message timestamps",
|
||||||
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
|
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
|
||||||
"Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms",
|
|
||||||
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
|
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
|
||||||
"Show avatars in user and room mentions": "Show avatars in user and room mentions",
|
"Show avatars in user and room mentions": "Show avatars in user and room mentions",
|
||||||
"Enable big emoji in chat": "Enable big emoji in chat",
|
"Enable big emoji in chat": "Enable big emoji in chat",
|
||||||
|
@ -652,12 +650,14 @@
|
||||||
"Cross-signing is ready for use.": "Cross-signing is ready for use.",
|
"Cross-signing is ready for use.": "Cross-signing is ready for use.",
|
||||||
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
|
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
|
||||||
"Cross-signing is not set up.": "Cross-signing is not set up.",
|
"Cross-signing is not set up.": "Cross-signing is not set up.",
|
||||||
|
"Set up": "Set up",
|
||||||
"Reset": "Reset",
|
"Reset": "Reset",
|
||||||
"Cross-signing public keys:": "Cross-signing public keys:",
|
"Cross-signing public keys:": "Cross-signing public keys:",
|
||||||
"in memory": "in memory",
|
"in memory": "in memory",
|
||||||
"not found": "not found",
|
"not found": "not found",
|
||||||
"Cross-signing private keys:": "Cross-signing private keys:",
|
"Cross-signing private keys:": "Cross-signing private keys:",
|
||||||
"in secret storage": "in secret storage",
|
"in secret storage": "in secret storage",
|
||||||
|
"not found in storage": "not found in storage",
|
||||||
"Master private key:": "Master private key:",
|
"Master private key:": "Master private key:",
|
||||||
"cached locally": "cached locally",
|
"cached locally": "cached locally",
|
||||||
"not found locally": "not found locally",
|
"not found locally": "not found locally",
|
||||||
|
@ -832,9 +832,9 @@
|
||||||
"Account management": "Account management",
|
"Account management": "Account management",
|
||||||
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
|
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
|
||||||
"Deactivate Account": "Deactivate Account",
|
"Deactivate Account": "Deactivate Account",
|
||||||
"General": "General",
|
|
||||||
"Discovery": "Discovery",
|
|
||||||
"Deactivate account": "Deactivate account",
|
"Deactivate account": "Deactivate account",
|
||||||
|
"Discovery": "Discovery",
|
||||||
|
"General": "General",
|
||||||
"Legal": "Legal",
|
"Legal": "Legal",
|
||||||
"Credits": "Credits",
|
"Credits": "Credits",
|
||||||
"For help with using %(brand)s, click <a>here</a>.": "For help with using %(brand)s, click <a>here</a>.",
|
"For help with using %(brand)s, click <a>here</a>.": "For help with using %(brand)s, click <a>here</a>.",
|
||||||
|
@ -1171,12 +1171,6 @@
|
||||||
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
||||||
"Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.",
|
"Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.",
|
||||||
"%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
|
"%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
|
||||||
"Start using Key Backup": "Start using Key Backup",
|
|
||||||
"Never lose encrypted messages": "Never lose encrypted messages",
|
|
||||||
"Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
|
||||||
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
|
|
||||||
"Not now": "Not now",
|
|
||||||
"Don't ask me again": "Don't ask me again",
|
|
||||||
"Appearance": "Appearance",
|
"Appearance": "Appearance",
|
||||||
"Show rooms with unread messages first": "Show rooms with unread messages first",
|
"Show rooms with unread messages first": "Show rooms with unread messages first",
|
||||||
"Show previews of messages": "Show previews of messages",
|
"Show previews of messages": "Show previews of messages",
|
||||||
|
@ -1630,9 +1624,6 @@
|
||||||
"Invite people to join %(communityName)s": "Invite people to join %(communityName)s",
|
"Invite people to join %(communityName)s": "Invite people to join %(communityName)s",
|
||||||
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
|
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
|
||||||
"Removing…": "Removing…",
|
"Removing…": "Removing…",
|
||||||
"Destroy cross-signing keys?": "Destroy cross-signing keys?",
|
|
||||||
"Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.",
|
|
||||||
"Clear cross-signing keys": "Clear cross-signing keys",
|
|
||||||
"Confirm Removal": "Confirm Removal",
|
"Confirm Removal": "Confirm Removal",
|
||||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||||
"Clear all data in this session?": "Clear all data in this session?",
|
"Clear all data in this session?": "Clear all data in this session?",
|
||||||
|
@ -1732,9 +1723,11 @@
|
||||||
"Recently Direct Messaged": "Recently Direct Messaged",
|
"Recently Direct Messaged": "Recently Direct Messaged",
|
||||||
"Direct Messages": "Direct Messages",
|
"Direct Messages": "Direct Messages",
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
"Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>.": "Start a conversation with someone using their name, username (like <userId/>) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>.",
|
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
|
||||||
|
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
|
||||||
"Go": "Go",
|
"Go": "Go",
|
||||||
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
|
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
|
||||||
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
||||||
"a new master key signature": "a new master key signature",
|
"a new master key signature": "a new master key signature",
|
||||||
"a new cross-signing key signature": "a new cross-signing key signature",
|
"a new cross-signing key signature": "a new cross-signing key signature",
|
||||||
"a device cross-signing signature": "a device cross-signing signature",
|
"a device cross-signing signature": "a device cross-signing signature",
|
||||||
|
@ -1752,6 +1745,7 @@
|
||||||
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||||
"Updating %(brand)s": "Updating %(brand)s",
|
"Updating %(brand)s": "Updating %(brand)s",
|
||||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
||||||
|
"Start using Key Backup": "Start using Key Backup",
|
||||||
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
||||||
"Manually export keys": "Manually export keys",
|
"Manually export keys": "Manually export keys",
|
||||||
"You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
|
"You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
|
||||||
|
@ -1885,6 +1879,13 @@
|
||||||
"Enter your Security Phrase or <button>Use your Security Key</button> to continue.": "Enter your Security Phrase or <button>Use your Security Key</button> to continue.",
|
"Enter your Security Phrase or <button>Use your Security Key</button> to continue.": "Enter your Security Phrase or <button>Use your Security Key</button> to continue.",
|
||||||
"Security Key": "Security Key",
|
"Security Key": "Security Key",
|
||||||
"Use your Security Key to continue.": "Use your Security Key to continue.",
|
"Use your Security Key to continue.": "Use your Security Key to continue.",
|
||||||
|
"Destroy cross-signing keys?": "Destroy cross-signing keys?",
|
||||||
|
"Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.",
|
||||||
|
"Clear cross-signing keys": "Clear cross-signing keys",
|
||||||
|
"Confirm encryption setup": "Confirm encryption setup",
|
||||||
|
"Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.",
|
||||||
|
"Unable to set up keys": "Unable to set up keys",
|
||||||
|
"Retry": "Retry",
|
||||||
"Restoring keys from backup": "Restoring keys from backup",
|
"Restoring keys from backup": "Restoring keys from backup",
|
||||||
"Fetching keys from server...": "Fetching keys from server...",
|
"Fetching keys from server...": "Fetching keys from server...",
|
||||||
"%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored",
|
"%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored",
|
||||||
|
@ -2234,6 +2235,57 @@
|
||||||
"Room Autocomplete": "Room Autocomplete",
|
"Room Autocomplete": "Room Autocomplete",
|
||||||
"Users": "Users",
|
"Users": "Users",
|
||||||
"User Autocomplete": "User Autocomplete",
|
"User Autocomplete": "User Autocomplete",
|
||||||
|
"We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.",
|
||||||
|
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
|
||||||
|
"Enter a recovery passphrase": "Enter a recovery passphrase",
|
||||||
|
"Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.",
|
||||||
|
"Set up with a recovery key": "Set up with a recovery key",
|
||||||
|
"That matches!": "That matches!",
|
||||||
|
"Use a different passphrase?": "Use a different passphrase?",
|
||||||
|
"That doesn't match.": "That doesn't match.",
|
||||||
|
"Go back to set it again.": "Go back to set it again.",
|
||||||
|
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
|
||||||
|
"Repeat your recovery passphrase...": "Repeat your recovery passphrase...",
|
||||||
|
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.",
|
||||||
|
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.",
|
||||||
|
"Your recovery key": "Your recovery key",
|
||||||
|
"Download": "Download",
|
||||||
|
"Your recovery key has been <b>copied to your clipboard</b>, paste it to:": "Your recovery key has been <b>copied to your clipboard</b>, paste it to:",
|
||||||
|
"Your recovery key is in your <b>Downloads</b> folder.": "Your recovery key is in your <b>Downloads</b> folder.",
|
||||||
|
"<b>Print it</b> and store it somewhere safe": "<b>Print it</b> and store it somewhere safe",
|
||||||
|
"<b>Save it</b> on a USB key or backup drive": "<b>Save it</b> on a USB key or backup drive",
|
||||||
|
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
|
||||||
|
"Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).",
|
||||||
|
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.",
|
||||||
|
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
|
||||||
|
"Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase",
|
||||||
|
"Confirm your recovery passphrase": "Confirm your recovery passphrase",
|
||||||
|
"Make a copy of your recovery key": "Make a copy of your recovery key",
|
||||||
|
"Starting backup...": "Starting backup...",
|
||||||
|
"Success!": "Success!",
|
||||||
|
"Create key backup": "Create key backup",
|
||||||
|
"Unable to create key backup": "Unable to create key backup",
|
||||||
|
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
|
||||||
|
"Generate a Security Key": "Generate a Security Key",
|
||||||
|
"We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.",
|
||||||
|
"Enter a Security Phrase": "Enter a Security Phrase",
|
||||||
|
"Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use a secret phrase only you know, and optionally save a Security Key to use for backup.",
|
||||||
|
"Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:",
|
||||||
|
"Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption",
|
||||||
|
"Restore": "Restore",
|
||||||
|
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
|
||||||
|
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
|
||||||
|
"Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.",
|
||||||
|
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
|
||||||
|
"Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.",
|
||||||
|
"Unable to query secret storage status": "Unable to query secret storage status",
|
||||||
|
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
|
||||||
|
"You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.",
|
||||||
|
"Upgrade your encryption": "Upgrade your encryption",
|
||||||
|
"Set a Security Phrase": "Set a Security Phrase",
|
||||||
|
"Confirm Security Phrase": "Confirm Security Phrase",
|
||||||
|
"Save your Security Key": "Save your Security Key",
|
||||||
|
"Unable to set up secret storage": "Unable to set up secret storage",
|
||||||
"Passphrases must match": "Passphrases must match",
|
"Passphrases must match": "Passphrases must match",
|
||||||
"Passphrase must not be empty": "Passphrase must not be empty",
|
"Passphrase must not be empty": "Passphrase must not be empty",
|
||||||
"Unknown error": "Unknown error",
|
"Unknown error": "Unknown error",
|
||||||
|
@ -2248,64 +2300,6 @@
|
||||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
|
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
|
||||||
"File to import": "File to import",
|
"File to import": "File to import",
|
||||||
"Import": "Import",
|
"Import": "Import",
|
||||||
"Confirm encryption setup": "Confirm encryption setup",
|
|
||||||
"Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.",
|
|
||||||
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
|
|
||||||
"Generate a Security Key": "Generate a Security Key",
|
|
||||||
"We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.",
|
|
||||||
"Enter a Security Phrase": "Enter a Security Phrase",
|
|
||||||
"Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use a secret phrase only you know, and optionally save a Security Key to use for backup.",
|
|
||||||
"Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:",
|
|
||||||
"Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption",
|
|
||||||
"Restore": "Restore",
|
|
||||||
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
|
|
||||||
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
|
|
||||||
"Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.",
|
|
||||||
"Enter a recovery passphrase": "Enter a recovery passphrase",
|
|
||||||
"Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.",
|
|
||||||
"That matches!": "That matches!",
|
|
||||||
"Use a different passphrase?": "Use a different passphrase?",
|
|
||||||
"That doesn't match.": "That doesn't match.",
|
|
||||||
"Go back to set it again.": "Go back to set it again.",
|
|
||||||
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
|
|
||||||
"Confirm your recovery passphrase": "Confirm your recovery passphrase",
|
|
||||||
"Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.",
|
|
||||||
"Download": "Download",
|
|
||||||
"Unable to query secret storage status": "Unable to query secret storage status",
|
|
||||||
"Retry": "Retry",
|
|
||||||
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
|
|
||||||
"You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.",
|
|
||||||
"Set up Secure Backup": "Set up Secure Backup",
|
|
||||||
"Upgrade your encryption": "Upgrade your encryption",
|
|
||||||
"Set a Security Phrase": "Set a Security Phrase",
|
|
||||||
"Confirm Security Phrase": "Confirm Security Phrase",
|
|
||||||
"Save your Security Key": "Save your Security Key",
|
|
||||||
"Unable to set up secret storage": "Unable to set up secret storage",
|
|
||||||
"We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.",
|
|
||||||
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
|
|
||||||
"Set up with a recovery key": "Set up with a recovery key",
|
|
||||||
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
|
|
||||||
"Repeat your recovery passphrase...": "Repeat your recovery passphrase...",
|
|
||||||
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.",
|
|
||||||
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.",
|
|
||||||
"Your recovery key": "Your recovery key",
|
|
||||||
"Your recovery key has been <b>copied to your clipboard</b>, paste it to:": "Your recovery key has been <b>copied to your clipboard</b>, paste it to:",
|
|
||||||
"Your recovery key is in your <b>Downloads</b> folder.": "Your recovery key is in your <b>Downloads</b> folder.",
|
|
||||||
"<b>Print it</b> and store it somewhere safe": "<b>Print it</b> and store it somewhere safe",
|
|
||||||
"<b>Save it</b> on a USB key or backup drive": "<b>Save it</b> on a USB key or backup drive",
|
|
||||||
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
|
|
||||||
"Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).",
|
|
||||||
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.",
|
|
||||||
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
|
|
||||||
"Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase",
|
|
||||||
"Make a copy of your recovery key": "Make a copy of your recovery key",
|
|
||||||
"Starting backup...": "Starting backup...",
|
|
||||||
"Success!": "Success!",
|
|
||||||
"Create key backup": "Create key backup",
|
|
||||||
"Unable to create key backup": "Unable to create key backup",
|
|
||||||
"Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.",
|
|
||||||
"If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.",
|
|
||||||
"Don't ask again": "Don't ask again",
|
|
||||||
"New Recovery Method": "New Recovery Method",
|
"New Recovery Method": "New Recovery Method",
|
||||||
"A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.",
|
"A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.",
|
||||||
"If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
|
"If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
|
||||||
|
|
|
@ -112,6 +112,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
|
||||||
body.append("secret_storage_ready", String(await client.isSecretStorageReady()));
|
body.append("secret_storage_ready", String(await client.isSecretStorageReady()));
|
||||||
body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey())));
|
body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey())));
|
||||||
|
|
||||||
|
body.append("session_backup_key_in_secret_storage", String(!!(await client.isKeyBackupKeyStored())));
|
||||||
const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey();
|
const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey();
|
||||||
body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache));
|
body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache));
|
||||||
body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array));
|
body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array));
|
||||||
|
|
|
@ -281,11 +281,6 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
displayName: _td('Autoplay GIFs and videos'),
|
displayName: _td('Autoplay GIFs and videos'),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
"showRoomRecoveryReminder": {
|
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
|
||||||
displayName: _td('Show a reminder to enable Secure Message Recovery in encrypted rooms'),
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
"enableSyntaxHighlightLanguageDetection": {
|
"enableSyntaxHighlightLanguageDetection": {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
displayName: _td('Enable automatic language detection for syntax highlighting'),
|
displayName: _td('Enable automatic language detection for syntax highlighting'),
|
||||||
|
@ -631,8 +626,36 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
supportedLevels: LEVELS_UI_FEATURE,
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
[UIFeature.Registration]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[UIFeature.PasswordReset]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[UIFeature.Deactivate]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[UIFeature.ShareQRCode]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[UIFeature.ShareSocial]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[UIFeature.IdentityServer]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
[UIFeature.ThirdPartyID]: {
|
[UIFeature.ThirdPartyID]: {
|
||||||
supportedLevels: LEVELS_UI_FEATURE,
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
[UIFeature.Flair]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,5 +20,12 @@ export enum UIFeature {
|
||||||
Widgets = "UIFeature.widgets",
|
Widgets = "UIFeature.widgets",
|
||||||
Voip = "UIFeature.voip",
|
Voip = "UIFeature.voip",
|
||||||
Feedback = "UIFeature.feedback",
|
Feedback = "UIFeature.feedback",
|
||||||
|
Registration = "UIFeature.registration",
|
||||||
|
PasswordReset = "UIFeature.passwordReset",
|
||||||
|
Deactivate = "UIFeature.deactivate",
|
||||||
|
ShareQRCode = "UIFeature.shareQrCode",
|
||||||
|
ShareSocial = "UIFeature.shareSocial",
|
||||||
|
IdentityServer = "UIFeature.identityServer",
|
||||||
ThirdPartyID = "UIFeature.thirdPartyId",
|
ThirdPartyID = "UIFeature.thirdPartyId",
|
||||||
|
Flair = "UIFeature.flair",
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Store} from 'flux/utils';
|
import {Store} from 'flux/utils';
|
||||||
|
import {MatrixError} from "matrix-js-sdk/src/http-api";
|
||||||
|
|
||||||
import dis from '../dispatcher/dispatcher';
|
import dis from '../dispatcher/dispatcher';
|
||||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||||
|
@ -26,6 +27,9 @@ import Modal from '../Modal';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
|
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
|
||||||
import {ActionPayload} from "../dispatcher/payloads";
|
import {ActionPayload} from "../dispatcher/payloads";
|
||||||
|
import {retry} from "../utils/promise";
|
||||||
|
|
||||||
|
const NUM_JOIN_RETRY = 5;
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
// Whether we're joining the currently viewed room (see isJoining())
|
// Whether we're joining the currently viewed room (see isJoining())
|
||||||
|
@ -259,24 +263,32 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private joinRoom(payload: ActionPayload) {
|
private async joinRoom(payload: ActionPayload) {
|
||||||
this.setState({
|
this.setState({
|
||||||
joining: true,
|
joining: true,
|
||||||
});
|
});
|
||||||
MatrixClientPeg.get().joinRoom(
|
|
||||||
this.state.roomAlias || this.state.roomId, payload.opts,
|
const cli = MatrixClientPeg.get();
|
||||||
).then(() => {
|
const address = this.state.roomAlias || this.state.roomId;
|
||||||
|
try {
|
||||||
|
await retry<void, MatrixError>(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => {
|
||||||
|
// if we received a Gateway timeout then retry
|
||||||
|
return err.httpStatus === 504;
|
||||||
|
});
|
||||||
|
|
||||||
// We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
|
// We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
|
||||||
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
|
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
|
||||||
// room.
|
// room.
|
||||||
dis.dispatch({ action: 'join_room_ready' });
|
dis.dispatch({ action: 'join_room_ready' });
|
||||||
}, (err) => {
|
} catch (err) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'join_room_error',
|
action: 'join_room_error',
|
||||||
err: err,
|
err: err,
|
||||||
});
|
});
|
||||||
|
|
||||||
let msg = err.message ? err.message : JSON.stringify(err);
|
let msg = err.message ? err.message : JSON.stringify(err);
|
||||||
console.log("Failed to join room:", msg);
|
console.log("Failed to join room:", msg);
|
||||||
|
|
||||||
if (err.name === "ConnectionError") {
|
if (err.name === "ConnectionError") {
|
||||||
msg = _t("There was an error joining the room");
|
msg = _t("There was an error joining the room");
|
||||||
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
|
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
|
||||||
|
@ -296,12 +308,13 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
|
||||||
title: _t("Failed to join room"),
|
title: _t("Failed to join room"),
|
||||||
description: msg,
|
description: msg,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getInvitingUserId(roomId: string): string {
|
private getInvitingUserId(roomId: string): string {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import Modal from "../Modal";
|
||||||
import * as sdk from "../index";
|
import * as sdk from "../index";
|
||||||
import { _t } from "../languageHandler";
|
import { _t } from "../languageHandler";
|
||||||
import DeviceListener from "../DeviceListener";
|
import DeviceListener from "../DeviceListener";
|
||||||
import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog";
|
import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEncryptionDialog";
|
||||||
import { accessSecretStorage } from "../SecurityManager";
|
import { accessSecretStorage } from "../SecurityManager";
|
||||||
import ToastStore from "../stores/ToastStore";
|
import ToastStore from "../stores/ToastStore";
|
||||||
import GenericToast from "../components/views/toasts/GenericToast";
|
import GenericToast from "../components/views/toasts/GenericToast";
|
||||||
|
@ -28,7 +28,7 @@ const TOAST_KEY = "setupencryption";
|
||||||
const getTitle = (kind: Kind) => {
|
const getTitle = (kind: Kind) => {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case Kind.SET_UP_ENCRYPTION:
|
case Kind.SET_UP_ENCRYPTION:
|
||||||
return _t("Set up encryption");
|
return _t("Set up Secure Backup");
|
||||||
case Kind.UPGRADE_ENCRYPTION:
|
case Kind.UPGRADE_ENCRYPTION:
|
||||||
return _t("Encryption upgrade available");
|
return _t("Encryption upgrade available");
|
||||||
case Kind.VERIFY_THIS_SESSION:
|
case Kind.VERIFY_THIS_SESSION:
|
||||||
|
@ -36,10 +36,20 @@ const getTitle = (kind: Kind) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getIcon = (kind: Kind) => {
|
||||||
|
switch (kind) {
|
||||||
|
case Kind.SET_UP_ENCRYPTION:
|
||||||
|
case Kind.UPGRADE_ENCRYPTION:
|
||||||
|
return "secure_backup";
|
||||||
|
case Kind.VERIFY_THIS_SESSION:
|
||||||
|
return "verification_warning";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getSetupCaption = (kind: Kind) => {
|
const getSetupCaption = (kind: Kind) => {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case Kind.SET_UP_ENCRYPTION:
|
case Kind.SET_UP_ENCRYPTION:
|
||||||
return _t("Set up");
|
return _t("Continue");
|
||||||
case Kind.UPGRADE_ENCRYPTION:
|
case Kind.UPGRADE_ENCRYPTION:
|
||||||
return _t("Upgrade");
|
return _t("Upgrade");
|
||||||
case Kind.VERIFY_THIS_SESSION:
|
case Kind.VERIFY_THIS_SESSION:
|
||||||
|
@ -51,7 +61,7 @@ const getDescription = (kind: Kind) => {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case Kind.SET_UP_ENCRYPTION:
|
case Kind.SET_UP_ENCRYPTION:
|
||||||
case Kind.UPGRADE_ENCRYPTION:
|
case Kind.UPGRADE_ENCRYPTION:
|
||||||
return _t("Verify yourself & others to keep your chats safe");
|
return _t("Safeguard against losing access to encrypted messages & data");
|
||||||
case Kind.VERIFY_THIS_SESSION:
|
case Kind.VERIFY_THIS_SESSION:
|
||||||
return _t("Other users may not trust it");
|
return _t("Other users may not trust it");
|
||||||
}
|
}
|
||||||
|
@ -88,7 +98,7 @@ export const showToast = (kind: Kind) => {
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
key: TOAST_KEY,
|
key: TOAST_KEY,
|
||||||
title: getTitle(kind),
|
title: getTitle(kind),
|
||||||
icon: "verification_warning",
|
icon: getIcon(kind),
|
||||||
props: {
|
props: {
|
||||||
description: getDescription(kind),
|
description: getDescription(kind),
|
||||||
acceptLabel: getSetupCaption(kind),
|
acceptLabel: getSetupCaption(kind),
|
||||||
|
|
|
@ -68,3 +68,21 @@ export function allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFul
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to retry a Promise a given number of times or until a predicate fails
|
||||||
|
export async function retry<T, E extends Error>(fn: () => Promise<T>, num: number, predicate?: (e: E) => boolean) {
|
||||||
|
let lastErr: E;
|
||||||
|
for (let i = 0; i < num; i++) {
|
||||||
|
try {
|
||||||
|
const v = await fn();
|
||||||
|
// If `await fn()` throws then we won't reach here
|
||||||
|
return v;
|
||||||
|
} catch (err) {
|
||||||
|
if (predicate && !predicate(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
lastErr = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastErr;
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import sdk from '../../../skinned-sdk';
|
||||||
import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
|
||||||
import { stubClient } from '../../../test-utils';
|
import { stubClient } from '../../../test-utils';
|
||||||
|
|
||||||
const AccessSecretStorageDialog = sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
|
const AccessSecretStorageDialog = sdk.getComponent("dialogs.security.AccessSecretStorageDialog");
|
||||||
|
|
||||||
describe("AccessSecretStorageDialog", function() {
|
describe("AccessSecretStorageDialog", function() {
|
||||||
it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => {
|
it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => {
|
||||||
|
|
|
@ -21,6 +21,7 @@ const {receiveMessage} = require('../usecases/timeline');
|
||||||
const {createDm} = require('../usecases/create-room');
|
const {createDm} = require('../usecases/create-room');
|
||||||
const {checkRoomSettings} = require('../usecases/room-settings');
|
const {checkRoomSettings} = require('../usecases/room-settings');
|
||||||
const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify');
|
const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify');
|
||||||
|
const { setupSecureBackup } = require('../usecases/security');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
module.exports = async function e2eEncryptionScenarios(alice, bob) {
|
module.exports = async function e2eEncryptionScenarios(alice, bob) {
|
||||||
|
@ -43,4 +44,5 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) {
|
||||||
const bobMessage = "You've got to tell me!";
|
const bobMessage = "You've got to tell me!";
|
||||||
await sendMessage(bob, bobMessage);
|
await sendMessage(bob, bobMessage);
|
||||||
await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true});
|
await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true});
|
||||||
|
await setupSecureBackup(alice);
|
||||||
};
|
};
|
||||||
|
|
42
test/end-to-end-tests/src/usecases/security.js
Normal file
42
test/end-to-end-tests/src/usecases/security.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { acceptToast } = require("./toasts");
|
||||||
|
|
||||||
|
async function setupSecureBackup(session) {
|
||||||
|
session.log.step("sets up Secure Backup");
|
||||||
|
|
||||||
|
await acceptToast(session, "Set up Secure Backup");
|
||||||
|
|
||||||
|
// Continue with the default (generate a security key)
|
||||||
|
const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
|
||||||
|
await xsignContButton.click();
|
||||||
|
|
||||||
|
//ignore the recovery key
|
||||||
|
//TODO: It's probably important for the tests to know the recovery key
|
||||||
|
const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn');
|
||||||
|
await copyButton.click();
|
||||||
|
|
||||||
|
//acknowledge that we copied the recovery key to a safe place
|
||||||
|
const copyContinueButton = await session.query(
|
||||||
|
'.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary',
|
||||||
|
);
|
||||||
|
await copyContinueButton.click();
|
||||||
|
|
||||||
|
session.log.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { setupSecureBackup };
|
|
@ -79,21 +79,6 @@ module.exports = async function signup(session, username, password, homeserver)
|
||||||
const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit');
|
const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit');
|
||||||
await acceptButton.click();
|
await acceptButton.click();
|
||||||
|
|
||||||
// Continue with the default (generate a security key)
|
|
||||||
const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
|
|
||||||
await xsignContButton.click();
|
|
||||||
|
|
||||||
//ignore the recovery key
|
|
||||||
//TODO: It's probably important for the tests to know the recovery key
|
|
||||||
const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn');
|
|
||||||
await copyButton.click();
|
|
||||||
|
|
||||||
//acknowledge that we copied the recovery key to a safe place
|
|
||||||
const copyContinueButton = await session.query(
|
|
||||||
'.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary',
|
|
||||||
);
|
|
||||||
await copyContinueButton.click();
|
|
||||||
|
|
||||||
//wait for registration to finish so the hash gets set
|
//wait for registration to finish so the hash gets set
|
||||||
//onhashchange better?
|
//onhashchange better?
|
||||||
|
|
||||||
|
|
63
yarn.lock
63
yarn.lock
|
@ -1907,17 +1907,7 @@ airbnb-prop-types@^2.15.0:
|
||||||
prop-types-exact "^1.2.0"
|
prop-types-exact "^1.2.0"
|
||||||
react-is "^16.9.0"
|
react-is "^16.9.0"
|
||||||
|
|
||||||
ajv-errors@^1.0.0:
|
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
|
|
||||||
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
|
|
||||||
|
|
||||||
ajv-keywords@^3.1.0:
|
|
||||||
version "3.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
|
|
||||||
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
|
|
||||||
|
|
||||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
|
|
||||||
version "6.12.2"
|
version "6.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
|
||||||
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
|
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
|
||||||
|
@ -2142,13 +2132,6 @@ async-limiter@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||||
|
|
||||||
async@^2.5.0:
|
|
||||||
version "2.6.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
|
|
||||||
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
|
|
||||||
dependencies:
|
|
||||||
lodash "^4.17.14"
|
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
@ -2294,11 +2277,6 @@ bcrypt-pbkdf@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
big.js@^5.2.2:
|
|
||||||
version "5.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
|
||||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
|
||||||
|
|
||||||
binary-extensions@^1.0.0:
|
binary-extensions@^1.0.0:
|
||||||
version "1.13.1"
|
version "1.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
||||||
|
@ -3285,11 +3263,6 @@ emojibase-regex@^4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.0.1.tgz#a2cd4bbb42825422da9ec72f15e970bc2c90b46a"
|
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.0.1.tgz#a2cd4bbb42825422da9ec72f15e970bc2c90b46a"
|
||||||
integrity sha512-S42UHkFfz15i4NNz+wi9iMKFo+B6Kalc6PJLpYX0BUANViXw4vSyYZMFdBGRLduSabWHuEcTLZl9xOa2YP3eJw==
|
integrity sha512-S42UHkFfz15i4NNz+wi9iMKFo+B6Kalc6PJLpYX0BUANViXw4vSyYZMFdBGRLduSabWHuEcTLZl9xOa2YP3eJw==
|
||||||
|
|
||||||
emojis-list@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
|
||||||
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
|
|
||||||
|
|
||||||
encoding@^0.1.11:
|
encoding@^0.1.11:
|
||||||
version "0.1.12"
|
version "0.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||||
|
@ -4018,14 +3991,6 @@ file-entry-cache@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache "^2.0.1"
|
flat-cache "^2.0.1"
|
||||||
|
|
||||||
file-loader@^3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa"
|
|
||||||
integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==
|
|
||||||
dependencies:
|
|
||||||
loader-utils "^1.0.2"
|
|
||||||
schema-utils "^1.0.0"
|
|
||||||
|
|
||||||
file-saver@^1.3.8:
|
file-saver@^1.3.8:
|
||||||
version "1.3.8"
|
version "1.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
|
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
|
||||||
|
@ -5746,15 +5711,6 @@ load-json-file@^4.0.0:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
loader-utils@^1.0.2, loader-utils@^1.1.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
|
||||||
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
|
||||||
dependencies:
|
|
||||||
big.js "^5.2.2"
|
|
||||||
emojis-list "^3.0.0"
|
|
||||||
json5 "^1.0.1"
|
|
||||||
|
|
||||||
locate-path@^2.0.0:
|
locate-path@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||||
|
@ -7684,15 +7640,6 @@ scheduler@^0.19.1:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
schema-utils@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
|
||||||
integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
|
|
||||||
dependencies:
|
|
||||||
ajv "^6.1.0"
|
|
||||||
ajv-errors "^1.0.0"
|
|
||||||
ajv-keywords "^3.1.0"
|
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
|
@ -7850,14 +7797,6 @@ socks@~2.3.2:
|
||||||
ip "1.1.5"
|
ip "1.1.5"
|
||||||
smart-buffer "^4.1.0"
|
smart-buffer "^4.1.0"
|
||||||
|
|
||||||
source-map-loader@^0.2.4:
|
|
||||||
version "0.2.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271"
|
|
||||||
integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==
|
|
||||||
dependencies:
|
|
||||||
async "^2.5.0"
|
|
||||||
loader-utils "^1.1.0"
|
|
||||||
|
|
||||||
source-map-resolve@^0.5.0:
|
source-map-resolve@^0.5.0:
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
|
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
|
||||||
|
|
Loading…
Reference in a new issue