You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-04 11:51:45 +03:00 
			
		
		
		
	Convert things to Typescript and re-use a generic component
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
		@@ -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<string>) {
 | 
			
		||||
        for (const d of deviceIds) {
 | 
			
		||||
            this.dismissed.add(d);
 | 
			
		||||
        }
 | 
			
		||||
@@ -181,52 +186,26 @@ 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
 | 
			
		||||
            // 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)
 | 
			
		||||
                    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,
 | 
			
		||||
                    });
 | 
			
		||||
                showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
 | 
			
		||||
            } 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,
 | 
			
		||||
                        });
 | 
			
		||||
                    showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION);
 | 
			
		||||
                } 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,
 | 
			
		||||
                        });
 | 
			
		||||
                    showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            } else {
 | 
			
		||||
                // cross-signing is ready, and we don't need to upgrade encryption
 | 
			
		||||
                ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This needs to be done after awaiting on downloadKeys() above, so
 | 
			
		||||
@@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/components/views/toasts/GenericToast.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/components/views/toasts/GenericToast.tsx
									
									
									
									
									
										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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										58
									
								
								src/toasts/BulkUnverifiedSessionsToast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/toasts/BulkUnverifiedSessionsToast.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										106
									
								
								src/toasts/SetupEncryptionToast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/toasts/SetupEncryptionToast.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										70
									
								
								src/toasts/UnverifiedSessionToast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/toasts/UnverifiedSessionToast.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user