From 14cee413603ce4fc36bdf1ac2f2b0551fe6b4516 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 22 May 2020 13:29:53 +0100 Subject: [PATCH] Convert things to Typescript and re-use a generic component Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/DeviceListener.ts | 111 ++++++------------ .../toasts/BulkUnverifiedSessionsToast.js | 56 --------- src/components/views/toasts/GenericToast.tsx | 42 +++++++ .../views/toasts/SetupEncryptionToast.js | 88 -------------- .../views/toasts/UnverifiedSessionToast.js | 66 ----------- ...tToast.js => VerificationRequestToast.tsx} | 48 +++++--- src/toasts/BulkUnverifiedSessionsToast.ts | 58 +++++++++ src/toasts/SetupEncryptionToast.ts | 106 +++++++++++++++++ src/toasts/UnverifiedSessionToast.ts | 70 +++++++++++ 9 files changed, 342 insertions(+), 303 deletions(-) delete mode 100644 src/components/views/toasts/BulkUnverifiedSessionsToast.js create mode 100644 src/components/views/toasts/GenericToast.tsx delete mode 100644 src/components/views/toasts/SetupEncryptionToast.js delete mode 100644 src/components/views/toasts/UnverifiedSessionToast.js rename src/components/views/toasts/{VerificationRequestToast.js => VerificationRequestToast.tsx} (86%) create mode 100644 src/toasts/BulkUnverifiedSessionsToast.ts create mode 100644 src/toasts/SetupEncryptionToast.ts create mode 100644 src/toasts/UnverifiedSessionToast.ts diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 1ecaca9b40..ca51b5ac1c 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -14,19 +14,24 @@ 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') @@ -82,7 +87,7 @@ export default class DeviceListener { * * @param {String[]} deviceIds List of device IDs to dismiss notifications for */ - async dismissUnverifiedSessions(deviceIds: string[]) { + async dismissUnverifiedSessions(deviceIds: Iterable) { for (const d of deviceIds) { this.dismissed.add(d); } @@ -181,51 +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"), - priority: 95, - }); - } 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"), - priority: 40, - }); - } 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"), - priority: 40, - }); - } - } + // 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); + } } } @@ -261,36 +240,20 @@ 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", - props: { - deviceIds: oldUnverifiedDeviceIds, - }, - component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"), - priority: 50, - }); + 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"), - priority: 80, - }); + showUnverifiedSessionsToast(deviceId); } // ...and hide any we don't need any more for (const deviceId of this.displayingToastsForDeviceIds) { if (!newUnverifiedDeviceIds.has(deviceId)) { - ToastStore.sharedInstance().dismissToast(toastKey(deviceId)); + hideUnverifiedSessionsToast(deviceId); } } diff --git a/src/components/views/toasts/BulkUnverifiedSessionsToast.js b/src/components/views/toasts/BulkUnverifiedSessionsToast.js deleted file mode 100644 index 99ff529c35..0000000000 --- a/src/components/views/toasts/BulkUnverifiedSessionsToast.js +++ /dev/null @@ -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 (
-
- {_t("Verify all your sessions to ensure your account & messages are safe")} -
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx new file mode 100644 index 0000000000..9d69330857 --- /dev/null +++ b/src/components/views/toasts/GenericToast.tsx @@ -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 = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => { + return
+
+ { description } +
+
+ {onReject && rejectLabel && } + +
+
; +}; + +export default GenericToast; diff --git a/src/components/views/toasts/SetupEncryptionToast.js b/src/components/views/toasts/SetupEncryptionToast.js deleted file mode 100644 index b5510e85b6..0000000000 --- a/src/components/views/toasts/SetupEncryptionToast.js +++ /dev/null @@ -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 (
-
{this.getDescription()}
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/UnverifiedSessionToast.js b/src/components/views/toasts/UnverifiedSessionToast.js deleted file mode 100644 index 38cd9f20df..0000000000 --- a/src/components/views/toasts/UnverifiedSessionToast.js +++ /dev/null @@ -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 (
-
- {_t( - "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})} -
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.tsx similarity index 86% rename from src/components/views/toasts/VerificationRequestToast.js rename to src/components/views/toasts/VerificationRequestToast.tsx index 421dd7bea1..38e7e31989 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.tsx @@ -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 { + 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 (
-
{nameLabel}
-
- - -
-
); + + return ; } } - -VerificationRequestToast.propTypes = { - request: PropTypes.object.isRequired, - toastKey: PropTypes.string.isRequired, -}; diff --git a/src/toasts/BulkUnverifiedSessionsToast.ts b/src/toasts/BulkUnverifiedSessionsToast.ts new file mode 100644 index 0000000000..41717e0804 --- /dev/null +++ b/src/toasts/BulkUnverifiedSessionsToast.ts @@ -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) => { + 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); +}; diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts new file mode 100644 index 0000000000..d35bbf1c88 --- /dev/null +++ b/src/toasts/SetupEncryptionToast.ts @@ -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); +}; diff --git a/src/toasts/UnverifiedSessionToast.ts b/src/toasts/UnverifiedSessionToast.ts new file mode 100644 index 0000000000..635356b9db --- /dev/null +++ b/src/toasts/UnverifiedSessionToast.ts @@ -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); +};