mirror of https://github.com/vector-im/riot-web
Merge pull request #4618 from matrix-org/t3chguy/toasts3
Migrate Toasts to Typescript and to granular priority systempull/21833/head
commit
b7428fcf35
|
@ -120,6 +120,7 @@
|
|||
"@types/classnames": "^2.2.10",
|
||||
"@types/flux": "^3.1.9",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/node": "^12.12.41",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"@types/react": "16.9",
|
||||
"@types/zxcvbn": "^4.4.0",
|
||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
import * as ModernizrStatic from "modernizr";
|
||||
import ContentMessages from "../ContentMessages";
|
||||
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import DeviceListener from "../DeviceListener";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -27,6 +29,8 @@ declare global {
|
|||
};
|
||||
|
||||
mx_ContentMessages: ContentMessages;
|
||||
mx_ToastStore: ToastStore;
|
||||
mx_DeviceListener: DeviceListener;
|
||||
}
|
||||
|
||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||
|
|
|
@ -14,43 +14,43 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||
import SettingsStore from './settings/SettingsStore';
|
||||
import * as sdk from './index';
|
||||
import { _t } from './languageHandler';
|
||||
import ToastStore from './stores/ToastStore';
|
||||
import {
|
||||
hideToast as hideBulkUnverifiedSessionsToast,
|
||||
showToast as showBulkUnverifiedSessionsToast
|
||||
} from "./toasts/BulkUnverifiedSessionsToast";
|
||||
import {
|
||||
hideToast as hideSetupEncryptionToast,
|
||||
Kind as SetupKind,
|
||||
Kind,
|
||||
showToast as showSetupEncryptionToast
|
||||
} from "./toasts/SetupEncryptionToast";
|
||||
import {
|
||||
hideToast as hideUnverifiedSessionsToast,
|
||||
showToast as showUnverifiedSessionsToast
|
||||
} from "./toasts/UnverifiedSessionToast";
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
const THIS_DEVICE_TOAST_KEY = 'setupencryption';
|
||||
const OTHER_DEVICES_TOAST_KEY = 'reviewsessions';
|
||||
|
||||
function toastKey(deviceId) {
|
||||
return "unverified_session_" + deviceId;
|
||||
}
|
||||
|
||||
export default class DeviceListener {
|
||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||
private dismissed = new Set<string>();
|
||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||
private dismissedThisDeviceToast = false;
|
||||
// cache of the key backup info
|
||||
private keyBackupInfo: object = null;
|
||||
private keyBackupFetchedAt: number = null;
|
||||
// We keep a list of our own device IDs so we can batch ones that were already
|
||||
// there the last time the app launched into a single toast, but display new
|
||||
// ones in their own toasts.
|
||||
private ourDeviceIdsAtStart: Set<string> = null;
|
||||
// The set of device IDs we're currently displaying toasts for
|
||||
private displayingToastsForDeviceIds = new Set<string>();
|
||||
|
||||
static sharedInstance() {
|
||||
if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
|
||||
return global.mx_DeviceListener;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||
this._dismissed = new Set();
|
||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||
this._dismissedThisDeviceToast = false;
|
||||
|
||||
// cache of the key backup info
|
||||
this._keyBackupInfo = null;
|
||||
this._keyBackupFetchedAt = null;
|
||||
|
||||
// We keep a list of our own device IDs so we can batch ones that were already
|
||||
// there the last time the app launched into a single toast, but display new
|
||||
// ones in their own toasts.
|
||||
this._ourDeviceIdsAtStart = null;
|
||||
|
||||
// The set of device IDs we're currently displaying toasts for
|
||||
this._displayingToastsForDeviceIds = new Set();
|
||||
if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener();
|
||||
return window.mx_DeviceListener;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -74,12 +74,12 @@ export default class DeviceListener {
|
|||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||
}
|
||||
this._dismissed.clear();
|
||||
this._dismissedThisDeviceToast = false;
|
||||
this._keyBackupInfo = null;
|
||||
this._keyBackupFetchedAt = null;
|
||||
this._ourDeviceIdsAtStart = null;
|
||||
this._displayingToastsForDeviceIds = new Set();
|
||||
this.dismissed.clear();
|
||||
this.dismissedThisDeviceToast = false;
|
||||
this.keyBackupInfo = null;
|
||||
this.keyBackupFetchedAt = null;
|
||||
this.ourDeviceIdsAtStart = null;
|
||||
this.displayingToastsForDeviceIds = new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,29 +87,29 @@ export default class DeviceListener {
|
|||
*
|
||||
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
|
||||
*/
|
||||
async dismissUnverifiedSessions(deviceIds) {
|
||||
async dismissUnverifiedSessions(deviceIds: Iterable<string>) {
|
||||
for (const d of deviceIds) {
|
||||
this._dismissed.add(d);
|
||||
this.dismissed.add(d);
|
||||
}
|
||||
|
||||
this._recheck();
|
||||
}
|
||||
|
||||
dismissEncryptionSetup() {
|
||||
this._dismissedThisDeviceToast = true;
|
||||
this.dismissedThisDeviceToast = true;
|
||||
this._recheck();
|
||||
}
|
||||
|
||||
_ensureDeviceIdsAtStartPopulated() {
|
||||
if (this._ourDeviceIdsAtStart === null) {
|
||||
if (this.ourDeviceIdsAtStart === null) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this._ourDeviceIdsAtStart = new Set(
|
||||
this.ourDeviceIdsAtStart = new Set(
|
||||
cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_onWillUpdateDevices = async (users, initialFetch) => {
|
||||
_onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
|
||||
// If we didn't know about *any* devices before (ie. it's fresh login),
|
||||
// then they are all pre-existing devices, so ignore this and set the
|
||||
// devicesAtStart list to the devices that we see after the fetch.
|
||||
|
@ -122,17 +122,17 @@ export default class DeviceListener {
|
|||
// before we download any new ones.
|
||||
}
|
||||
|
||||
_onDevicesUpdated = (users) => {
|
||||
_onDevicesUpdated = (users: string[]) => {
|
||||
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||
this._recheck();
|
||||
}
|
||||
|
||||
_onDeviceVerificationChanged = (userId) => {
|
||||
_onDeviceVerificationChanged = (userId: string) => {
|
||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||
this._recheck();
|
||||
}
|
||||
|
||||
_onUserTrustStatusChanged = (userId, trustLevel) => {
|
||||
_onUserTrustStatusChanged = (userId: string) => {
|
||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||
this._recheck();
|
||||
}
|
||||
|
@ -163,11 +163,11 @@ export default class DeviceListener {
|
|||
// & cache the result
|
||||
async _getKeyBackupInfo() {
|
||||
const now = (new Date()).getTime();
|
||||
if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||
this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
this._keyBackupFetchedAt = now;
|
||||
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
this.keyBackupFetchedAt = now;
|
||||
}
|
||||
return this._keyBackupInfo;
|
||||
return this.keyBackupInfo;
|
||||
}
|
||||
|
||||
async _recheck() {
|
||||
|
@ -186,48 +186,25 @@ export default class DeviceListener {
|
|||
|
||||
const crossSigningReady = await cli.isCrossSigningReady();
|
||||
|
||||
if (this._dismissedThisDeviceToast) {
|
||||
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
|
||||
if (this.dismissedThisDeviceToast || crossSigningReady) {
|
||||
hideSetupEncryptionToast();
|
||||
} else {
|
||||
if (!crossSigningReady) {
|
||||
// make sure our keys are finished downlaoding
|
||||
await cli.downloadKeys([cli.getUserId()]);
|
||||
// cross signing isn't enabled - nag to enable it
|
||||
// There are 3 different toasts for:
|
||||
if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: THIS_DEVICE_TOAST_KEY,
|
||||
title: _t("Verify this session"),
|
||||
icon: "verification_warning",
|
||||
props: {kind: 'verify_this_session'},
|
||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
||||
});
|
||||
} else {
|
||||
const backupInfo = await this._getKeyBackupInfo();
|
||||
if (backupInfo) {
|
||||
// No cross-signing on account but key backup available (upgrade encryption)
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: THIS_DEVICE_TOAST_KEY,
|
||||
title: _t("Encryption upgrade available"),
|
||||
icon: "verification_warning",
|
||||
props: {kind: 'upgrade_encryption'},
|
||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
||||
});
|
||||
} else {
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: THIS_DEVICE_TOAST_KEY,
|
||||
title: _t("Set up encryption"),
|
||||
icon: "verification_warning",
|
||||
props: {kind: 'set_up_encryption'},
|
||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
||||
});
|
||||
}
|
||||
}
|
||||
// make sure our keys are finished downloading
|
||||
await cli.downloadKeys([cli.getUserId()]);
|
||||
// cross signing isn't enabled - nag to enable it
|
||||
// There are 3 different toasts for:
|
||||
if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||
} else {
|
||||
// cross-signing is ready, and we don't need to upgrade encryption
|
||||
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
|
||||
const backupInfo = await this._getKeyBackupInfo();
|
||||
if (backupInfo) {
|
||||
// No cross-signing on account but key backup available (upgrade encryption)
|
||||
showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION);
|
||||
} else {
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,20 +216,20 @@ export default class DeviceListener {
|
|||
// (technically could just be a boolean: we don't actually
|
||||
// need to remember the device IDs, but for the sake of
|
||||
// symmetry...).
|
||||
const oldUnverifiedDeviceIds = new Set();
|
||||
const oldUnverifiedDeviceIds = new Set<string>();
|
||||
// Unverified devices that have appeared since then
|
||||
const newUnverifiedDeviceIds = new Set();
|
||||
const newUnverifiedDeviceIds = new Set<string>();
|
||||
|
||||
// as long as cross-signing isn't ready,
|
||||
// you can't see or dismiss any device toasts
|
||||
if (crossSigningReady) {
|
||||
const devices = cli.getStoredDevicesForUser(cli.getUserId());
|
||||
for (const device of devices) {
|
||||
if (device.deviceId == cli.deviceId) continue;
|
||||
if (device.deviceId === cli.deviceId) continue;
|
||||
|
||||
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
|
||||
if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) {
|
||||
if (this._ourDeviceIdsAtStart.has(device.deviceId)) {
|
||||
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
|
||||
if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
|
||||
oldUnverifiedDeviceIds.add(device.deviceId);
|
||||
} else {
|
||||
newUnverifiedDeviceIds.add(device.deviceId);
|
||||
|
@ -263,38 +240,23 @@ export default class DeviceListener {
|
|||
|
||||
// Display or hide the batch toast for old unverified sessions
|
||||
if (oldUnverifiedDeviceIds.size > 0) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: OTHER_DEVICES_TOAST_KEY,
|
||||
title: _t("Review where you’re logged in"),
|
||||
icon: "verification_warning",
|
||||
priority: ToastStore.PRIORITY_LOW,
|
||||
props: {
|
||||
deviceIds: oldUnverifiedDeviceIds,
|
||||
},
|
||||
component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
|
||||
});
|
||||
showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);
|
||||
} else {
|
||||
ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
|
||||
hideBulkUnverifiedSessionsToast();
|
||||
}
|
||||
|
||||
// Show toasts for new unverified devices if they aren't already there
|
||||
for (const deviceId of newUnverifiedDeviceIds) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: toastKey(deviceId),
|
||||
title: _t("New login. Was this you?"),
|
||||
icon: "verification_warning",
|
||||
props: { deviceId },
|
||||
component: sdk.getComponent("toasts.UnverifiedSessionToast"),
|
||||
});
|
||||
showUnverifiedSessionsToast(deviceId);
|
||||
}
|
||||
|
||||
// ...and hide any we don't need any more
|
||||
for (const deviceId of this._displayingToastsForDeviceIds) {
|
||||
for (const deviceId of this.displayingToastsForDeviceIds) {
|
||||
if (!newUnverifiedDeviceIds.has(deviceId)) {
|
||||
ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
|
||||
hideUnverifiedSessionsToast(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
this._displayingToastsForDeviceIds = newUnverifiedDeviceIds;
|
||||
this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
|
||||
}
|
||||
}
|
|
@ -1570,7 +1570,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
icon: "verification",
|
||||
props: {request},
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
priority: ToastStore.PRIORITY_REALTIME,
|
||||
priority: 90,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,13 +15,21 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||
import classNames from "classnames";
|
||||
|
||||
export default class ToastContainer extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {toasts: ToastStore.sharedInstance().getToasts()};
|
||||
interface IState {
|
||||
toasts: IToast<any>[];
|
||||
countSeen: number;
|
||||
}
|
||||
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||
};
|
||||
|
||||
// Start listening here rather than in componentDidMount because
|
||||
// toasts may dismiss themselves in their didMount if they find
|
||||
|
@ -35,7 +43,10 @@ export default class ToastContainer extends React.Component {
|
|||
}
|
||||
|
||||
_onToastStoreUpdate = () => {
|
||||
this.setState({toasts: ToastStore.sharedInstance().getToasts()});
|
||||
this.setState({
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -51,8 +62,8 @@ export default class ToastContainer extends React.Component {
|
|||
});
|
||||
|
||||
let countIndicator;
|
||||
if (isStacked) {
|
||||
countIndicator = `(1/${totalCount})`;
|
||||
if (isStacked || this.state.countSeen > 0) {
|
||||
countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
|
||||
}
|
||||
|
||||
const toastProps = Object.assign({}, props, {
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
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 { _t } from '../../../languageHandler';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.BulkUnverifiedSessionsToast")
|
||||
export default class BulkUnverifiedSessionsToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceIds: PropTypes.array,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_user_info',
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
{_t("Verify all your sessions to ensure your account & messages are safe")}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Review")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import React, {ReactChild} from "react";
|
||||
|
||||
import FormButton from "../elements/FormButton";
|
||||
|
||||
interface IProps {
|
||||
description: ReactChild;
|
||||
acceptLabel: string;
|
||||
rejectLabel?: string;
|
||||
|
||||
onAccept();
|
||||
onReject?();
|
||||
}
|
||||
|
||||
const GenericToast: React.FC<IProps> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
|
||||
return <div>
|
||||
<div className="mx_Toast_description">
|
||||
{ description }
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
{onReject && rejectLabel && <FormButton label={rejectLabel} kind="danger" onClick={onReject} /> }
|
||||
<FormButton label={acceptLabel} onClick={onAccept} />
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default GenericToast;
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
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 Modal from '../../../Modal';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import SetupEncryptionDialog from "../dialogs/SetupEncryptionDialog";
|
||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
export default class SetupEncryptionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf([
|
||||
'set_up_encryption',
|
||||
'verify_this_session',
|
||||
'upgrade_encryption',
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissEncryptionSetup();
|
||||
};
|
||||
|
||||
_onSetupClick = async () => {
|
||||
if (this.props.kind === "verify_this_session") {
|
||||
Modal.createTrackedDialog('Verify session', 'Verify session', SetupEncryptionDialog,
|
||||
{}, null, /* priority = */ false, /* static = */ true);
|
||||
} else {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(
|
||||
Spinner, null, 'mx_Dialog_spinner', /* priority */ false, /* static */ true,
|
||||
);
|
||||
try {
|
||||
await accessSecretStorage();
|
||||
} finally {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getDescription() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
case 'upgrade_encryption':
|
||||
return _t('Verify yourself & others to keep your chats safe');
|
||||
case 'verify_this_session':
|
||||
return _t('Other users may not trust it');
|
||||
}
|
||||
}
|
||||
|
||||
getSetupCaption() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
return _t('Set up');
|
||||
case 'upgrade_encryption':
|
||||
return _t('Upgrade');
|
||||
case 'verify_this_session':
|
||||
return _t('Verify');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{this.getDescription()}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={this.getSetupCaption()} onClick={this._onSetupClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
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 { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.UnverifiedSessionToast")
|
||||
export default class UnverifiedSessionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceId: PropTypes.string,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
||||
userId: cli.getUserId(),
|
||||
device: cli.getStoredDevice(cli.getUserId(), this.props.deviceId),
|
||||
onFinished: (r) => {
|
||||
if (!r) {
|
||||
/* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
}
|
||||
},
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const device = cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
|
||||
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
{_t(
|
||||
"Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Verify")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from "react";
|
||||
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
|
@ -24,8 +24,23 @@ import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import ToastStore from "../../../stores/ToastStore";
|
||||
import Modal from "../../../Modal";
|
||||
import GenericToast from "./GenericToast";
|
||||
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
|
||||
interface IProps {
|
||||
toastKey: string;
|
||||
request: VerificationRequest;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
counter: number;
|
||||
device?: DeviceInfo;
|
||||
}
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent<IProps, IState> {
|
||||
private intervalHandle: NodeJS.Timeout;
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {counter: Math.ceil(props.request.timeout / 1000)};
|
||||
|
@ -34,7 +49,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
async componentDidMount() {
|
||||
const {request} = this.props;
|
||||
if (request.timeout && request.timeout > 0) {
|
||||
this._intervalHandle = setInterval(() => {
|
||||
this.intervalHandle = setInterval(() => {
|
||||
let {counter} = this.state;
|
||||
counter = Math.max(0, counter - 1);
|
||||
this.setState({counter});
|
||||
|
@ -56,7 +71,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this._intervalHandle);
|
||||
clearInterval(this.intervalHandle);
|
||||
const {request} = this.props;
|
||||
request.off("change", this._checkRequestIsPending);
|
||||
}
|
||||
|
@ -110,7 +125,6 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
const {request} = this.props;
|
||||
let nameLabel;
|
||||
if (request.isSelfVerification) {
|
||||
|
@ -133,20 +147,16 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
const declineLabel = this.state.counter == 0 ?
|
||||
const declineLabel = this.state.counter === 0 ?
|
||||
_t("Decline") :
|
||||
_t("Decline (%(counter)s)", {counter: this.state.counter});
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{nameLabel}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={declineLabel} kind="danger" onClick={this.cancel} />
|
||||
<FormButton label={_t("Accept")} onClick={this.accept} />
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
return <GenericToast
|
||||
description={nameLabel}
|
||||
acceptLabel={_t("Accept")}
|
||||
onAccept={this.accept}
|
||||
rejectLabel={declineLabel}
|
||||
onReject={this.cancel}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
VerificationRequestToast.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
};
|
|
@ -102,11 +102,6 @@
|
|||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||
"Verify this session": "Verify this session",
|
||||
"Encryption upgrade available": "Encryption upgrade available",
|
||||
"Set up encryption": "Set up encryption",
|
||||
"Review where you’re logged in": "Review where you’re logged in",
|
||||
"New login. Was this you?": "New login. Was this you?",
|
||||
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
||||
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
|
||||
"Invite new community members": "Invite new community members",
|
||||
|
@ -396,6 +391,20 @@
|
|||
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
||||
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
|
||||
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
|
||||
"Review where you’re logged in": "Review where you’re logged in",
|
||||
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
|
||||
"Review": "Review",
|
||||
"Later": "Later",
|
||||
"Set up encryption": "Set up encryption",
|
||||
"Encryption upgrade available": "Encryption upgrade available",
|
||||
"Verify this session": "Verify this session",
|
||||
"Set up": "Set up",
|
||||
"Upgrade": "Upgrade",
|
||||
"Verify": "Verify",
|
||||
"Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe",
|
||||
"Other users may not trust it": "Other users may not trust it",
|
||||
"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",
|
||||
"There was an error joining the room": "There was an error joining the room",
|
||||
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
|
||||
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
||||
|
@ -570,15 +579,6 @@
|
|||
"Headphones": "Headphones",
|
||||
"Folder": "Folder",
|
||||
"Pin": "Pin",
|
||||
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
|
||||
"Later": "Later",
|
||||
"Review": "Review",
|
||||
"Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe",
|
||||
"Other users may not trust it": "Other users may not trust it",
|
||||
"Set up": "Set up",
|
||||
"Upgrade": "Upgrade",
|
||||
"Verify": "Verify",
|
||||
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
|
||||
"From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
|
||||
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
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 EventEmitter from 'events';
|
||||
|
||||
/**
|
||||
* Holds the active toasts
|
||||
*/
|
||||
export default class ToastStore extends EventEmitter {
|
||||
static PRIORITY_REALTIME = 0;
|
||||
static PRIORITY_DEFAULT = 1;
|
||||
static PRIORITY_LOW = 2;
|
||||
|
||||
static sharedInstance() {
|
||||
if (!global.mx_ToastStore) global.mx_ToastStore = new ToastStore();
|
||||
return global.mx_ToastStore;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._dispatcherRef = null;
|
||||
this._toasts = [];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._toasts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or replace a toast
|
||||
* If a toast with the same toastKey already exists, the given toast will replace it
|
||||
* Toasts are always added underneath any toasts of the same priority, so existing
|
||||
* toasts stay at the top unless a higher priority one arrives (better to not change the
|
||||
* toast unless necessary).
|
||||
*
|
||||
* @param {boject} newToast The new toast
|
||||
*/
|
||||
addOrReplaceToast(newToast) {
|
||||
if (newToast.priority === undefined) newToast.priority = ToastStore.PRIORITY_DEFAULT;
|
||||
|
||||
const oldIndex = this._toasts.findIndex(t => t.key === newToast.key);
|
||||
if (oldIndex === -1) {
|
||||
let newIndex = this._toasts.length;
|
||||
while (newIndex > 0 && this._toasts[newIndex - 1].priority > newToast.priority) --newIndex;
|
||||
this._toasts.splice(newIndex, 0, newToast);
|
||||
} else {
|
||||
this._toasts[oldIndex] = newToast;
|
||||
}
|
||||
this.emit('update');
|
||||
}
|
||||
|
||||
dismissToast(key) {
|
||||
this._toasts = this._toasts.filter(t => t.key !== key);
|
||||
this.emit('update');
|
||||
}
|
||||
|
||||
getToasts() {
|
||||
return this._toasts;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
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 EventEmitter from "events";
|
||||
import React, {JSXElementConstructor} from "react";
|
||||
|
||||
export interface IToast<C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> {
|
||||
key: string;
|
||||
// higher priority number will be shown on top of lower priority
|
||||
priority: number;
|
||||
title: string;
|
||||
icon?: string;
|
||||
component: C;
|
||||
props?: React.ComponentProps<C>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the active toasts
|
||||
*/
|
||||
export default class ToastStore extends EventEmitter {
|
||||
private toasts: IToast<any>[] = [];
|
||||
// The count of toasts which have been seen & dealt with in this stack
|
||||
// where the count resets when the stack of toasts clears.
|
||||
private countSeen = 0;
|
||||
|
||||
static sharedInstance() {
|
||||
if (!window.mx_ToastStore) window.mx_ToastStore = new ToastStore();
|
||||
return window.mx_ToastStore;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.toasts = [];
|
||||
this.countSeen = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or replace a toast
|
||||
* If a toast with the same toastKey already exists, the given toast will replace it
|
||||
* Toasts are always added underneath any toasts of the same priority, so existing
|
||||
* toasts stay at the top unless a higher priority one arrives (better to not change the
|
||||
* toast unless necessary).
|
||||
*
|
||||
* @param {object} newToast The new toast
|
||||
*/
|
||||
addOrReplaceToast<C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>>(newToast: IToast<C>) {
|
||||
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
|
||||
if (oldIndex === -1) {
|
||||
let newIndex = this.toasts.length;
|
||||
while (newIndex > 0 && this.toasts[newIndex - 1].priority < newToast.priority) --newIndex;
|
||||
this.toasts.splice(newIndex, 0, newToast);
|
||||
} else {
|
||||
this.toasts[oldIndex] = newToast;
|
||||
}
|
||||
this.emit('update');
|
||||
}
|
||||
|
||||
dismissToast(key) {
|
||||
const length = this.toasts.length;
|
||||
this.toasts = this.toasts.filter(t => t.key !== key);
|
||||
if (length !== this.toasts.length) {
|
||||
if (this.toasts.length === 0) {
|
||||
this.countSeen = 0;
|
||||
} else {
|
||||
this.countSeen++;
|
||||
}
|
||||
|
||||
this.emit('update');
|
||||
}
|
||||
}
|
||||
|
||||
getToasts() {
|
||||
return this.toasts;
|
||||
}
|
||||
|
||||
getCountSeen() {
|
||||
return this.countSeen;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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 { _t } from '../languageHandler';
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import DeviceListener from '../DeviceListener';
|
||||
import GenericToast from "../components/views/toasts/GenericToast";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
|
||||
const TOAST_KEY = "reviewsessions";
|
||||
|
||||
export const showToast = (deviceIds: Set<string>) => {
|
||||
const onAccept = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_user_info',
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
});
|
||||
};
|
||||
|
||||
const onReject = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds);
|
||||
};
|
||||
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: TOAST_KEY,
|
||||
title: _t("Review where you’re logged in"),
|
||||
icon: "verification_warning",
|
||||
props: {
|
||||
description: _t("Verify all your sessions to ensure your account & messages are safe"),
|
||||
acceptLabel: _t("Review"),
|
||||
onAccept,
|
||||
rejectLabel: _t("Later"),
|
||||
onReject,
|
||||
},
|
||||
component: GenericToast,
|
||||
priority: 50,
|
||||
});
|
||||
};
|
||||
|
||||
export const hideToast = () => {
|
||||
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
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 Modal from "../Modal";
|
||||
import * as sdk from "../index";
|
||||
import { _t } from "../languageHandler";
|
||||
import DeviceListener from "../DeviceListener";
|
||||
import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog";
|
||||
import { accessSecretStorage } from "../CrossSigningManager";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import GenericToast from "../components/views/toasts/GenericToast";
|
||||
|
||||
const TOAST_KEY = "setupencryption";
|
||||
|
||||
const getTitle = (kind: Kind) => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
return _t("Set up encryption");
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("Encryption upgrade available");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("Verify this session");
|
||||
}
|
||||
};
|
||||
|
||||
const getSetupCaption = (kind: Kind) => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
return _t("Set up");
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("Upgrade");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("Verify");
|
||||
}
|
||||
};
|
||||
|
||||
const getDescription = (kind: Kind) => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("Verify yourself & others to keep your chats safe");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("Other users may not trust it");
|
||||
}
|
||||
};
|
||||
|
||||
export enum Kind {
|
||||
SET_UP_ENCRYPTION = "set_up_encryption",
|
||||
UPGRADE_ENCRYPTION = "upgrade_encryption",
|
||||
VERIFY_THIS_SESSION = "verify_this_session",
|
||||
}
|
||||
|
||||
const onReject = () => {
|
||||
DeviceListener.sharedInstance().dismissEncryptionSetup();
|
||||
};
|
||||
|
||||
export const showToast = (kind: Kind) => {
|
||||
const onAccept = async () => {
|
||||
if (kind === Kind.VERIFY_THIS_SESSION) {
|
||||
Modal.createTrackedDialog("Verify session", "Verify session", SetupEncryptionDialog,
|
||||
{}, null, /* priority = */ false, /* static = */ true);
|
||||
} else {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(
|
||||
Spinner, null, "mx_Dialog_spinner", /* priority */ false, /* static */ true,
|
||||
);
|
||||
try {
|
||||
await accessSecretStorage();
|
||||
} finally {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: TOAST_KEY,
|
||||
title: getTitle(kind),
|
||||
icon: "verification_warning",
|
||||
props: {
|
||||
description: getDescription(kind),
|
||||
acceptLabel: getSetupCaption(kind),
|
||||
onAccept,
|
||||
rejectLabel: _t("Later"),
|
||||
onReject,
|
||||
},
|
||||
component: GenericToast,
|
||||
priority: kind === Kind.VERIFY_THIS_SESSION ? 95 : 40,
|
||||
});
|
||||
};
|
||||
|
||||
export const hideToast = () => {
|
||||
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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 { _t } from '../languageHandler';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import Modal from '../Modal';
|
||||
import DeviceListener from '../DeviceListener';
|
||||
import NewSessionReviewDialog from '../components/views/dialogs/NewSessionReviewDialog';
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import GenericToast from "../components/views/toasts/GenericToast";
|
||||
|
||||
function toastKey(deviceId: string) {
|
||||
return "unverified_session_" + deviceId;
|
||||
}
|
||||
|
||||
export const showToast = (deviceId: string) => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
const onAccept = () => {
|
||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
||||
userId: cli.getUserId(),
|
||||
device: cli.getStoredDevice(cli.getUserId(), deviceId),
|
||||
onFinished: (r) => {
|
||||
if (!r) {
|
||||
/* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
||||
}
|
||||
},
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
const onReject = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
||||
};
|
||||
|
||||
const device = cli.getStoredDevice(cli.getUserId(), deviceId);
|
||||
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: toastKey(deviceId),
|
||||
title: _t("New login. Was this you?"),
|
||||
icon: "verification_warning",
|
||||
props: {
|
||||
description: _t(
|
||||
"Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()}),
|
||||
acceptLabel: _t("Verify"),
|
||||
onAccept,
|
||||
rejectLabel: _t("Later"),
|
||||
onReject,
|
||||
},
|
||||
component: GenericToast,
|
||||
priority: 80,
|
||||
});
|
||||
};
|
||||
|
||||
export const hideToast = (deviceId: string) => {
|
||||
ToastStore.sharedInstance().dismissToast(deviceId);
|
||||
};
|
|
@ -1280,6 +1280,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
|
||||
integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
|
||||
|
||||
"@types/node@^12.12.41":
|
||||
version "12.12.42"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.42.tgz#d0d1149336bd07540dd1ea576692829d575dec34"
|
||||
integrity sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
|
|
Loading…
Reference in New Issue