Refactor + improve test coverage for QR login (#9525)
This commit is contained in:
parent
9096bd82d6
commit
f05cc5d8f3
10 changed files with 1604 additions and 774 deletions
|
@ -22,14 +22,8 @@ import { logger } from 'matrix-js-sdk/src/logger';
|
|||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import QRCode from '../elements/QRCode';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import { Icon as BackButtonIcon } from "../../../../res/img/element-icons/back.svg";
|
||||
import { Icon as DevicesIcon } from "../../../../res/img/element-icons/devices.svg";
|
||||
import { Icon as WarningBadge } from "../../../../res/img/element-icons/warning-badge.svg";
|
||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
||||
import { wrapRequestWithDialog } from '../../../utils/UserInteractiveAuth';
|
||||
import LoginWithQRFlow from './LoginWithQRFlow';
|
||||
|
||||
/**
|
||||
* The intention of this enum is to have a mode that scans a QR code instead of generating one.
|
||||
|
@ -41,7 +35,7 @@ export enum Mode {
|
|||
Show = "show",
|
||||
}
|
||||
|
||||
enum Phase {
|
||||
export enum Phase {
|
||||
Loading,
|
||||
ShowingQR,
|
||||
Connecting,
|
||||
|
@ -51,6 +45,14 @@ enum Phase {
|
|||
Error,
|
||||
}
|
||||
|
||||
export enum Click {
|
||||
Cancel,
|
||||
Decline,
|
||||
Approve,
|
||||
TryAgain,
|
||||
Back,
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
client: MatrixClient;
|
||||
mode: Mode;
|
||||
|
@ -68,7 +70,7 @@ interface IState {
|
|||
/**
|
||||
* A component that allows sign in and E2EE set up with a QR code.
|
||||
*
|
||||
* It implements both `login.start` and `login-reciprocate` capabilities as well as both scanning and showing QR codes.
|
||||
* It implements `login.reciprocate` capabilities and showing QR codes.
|
||||
*
|
||||
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
||||
*/
|
||||
|
@ -138,6 +140,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({ phase: Phase.Verifying });
|
||||
await this.state.rendezvous.verifyNewDeviceOnExistingDevice();
|
||||
this.props.onFinished(true);
|
||||
} catch (e) {
|
||||
|
@ -197,200 +200,41 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
});
|
||||
}
|
||||
|
||||
private cancelClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private declineClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private tryAgainClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
this.reset();
|
||||
await this.updateMode(this.props.mode);
|
||||
};
|
||||
|
||||
private onBackClick = async () => {
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private cancelButton = () => <AccessibleButton
|
||||
kind="primary_outline"
|
||||
onClick={this.cancelClicked}
|
||||
>
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>;
|
||||
|
||||
private simpleSpinner = (description?: string): JSX.Element => {
|
||||
return <div className="mx_LoginWithQR_spinner">
|
||||
<div>
|
||||
<Spinner />
|
||||
{ description && <p>{ description }</p> }
|
||||
</div>
|
||||
</div>;
|
||||
private onClick = async (type: Click) => {
|
||||
switch (type) {
|
||||
case Click.Cancel:
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
break;
|
||||
case Click.Approve:
|
||||
await this.approveLogin();
|
||||
break;
|
||||
case Click.Decline:
|
||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
break;
|
||||
case Click.TryAgain:
|
||||
this.reset();
|
||||
await this.updateMode(this.props.mode);
|
||||
break;
|
||||
case Click.Back:
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
this.props.onFinished(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
let title: string;
|
||||
let titleIcon: JSX.Element | undefined;
|
||||
let main: JSX.Element | undefined;
|
||||
let buttons: JSX.Element | undefined;
|
||||
let backButton = true;
|
||||
let cancellationMessage: string | undefined;
|
||||
let centreTitle = false;
|
||||
|
||||
switch (this.state.phase) {
|
||||
case Phase.Error:
|
||||
switch (this.state.failureReason) {
|
||||
case RendezvousFailureReason.Expired:
|
||||
cancellationMessage = _t("The linking wasn't completed in the required time.");
|
||||
break;
|
||||
case RendezvousFailureReason.InvalidCode:
|
||||
cancellationMessage = _t("The scanned code is invalid.");
|
||||
break;
|
||||
case RendezvousFailureReason.UnsupportedAlgorithm:
|
||||
cancellationMessage = _t("Linking with this device is not supported.");
|
||||
break;
|
||||
case RendezvousFailureReason.UserDeclined:
|
||||
cancellationMessage = _t("The request was declined on the other device.");
|
||||
break;
|
||||
case RendezvousFailureReason.OtherDeviceAlreadySignedIn:
|
||||
cancellationMessage = _t("The other device is already signed in.");
|
||||
break;
|
||||
case RendezvousFailureReason.OtherDeviceNotSignedIn:
|
||||
cancellationMessage = _t("The other device isn't signed in.");
|
||||
break;
|
||||
case RendezvousFailureReason.UserCancelled:
|
||||
cancellationMessage = _t("The request was cancelled.");
|
||||
break;
|
||||
case RendezvousFailureReason.Unknown:
|
||||
cancellationMessage = _t("An unexpected error occurred.");
|
||||
break;
|
||||
case RendezvousFailureReason.HomeserverLacksSupport:
|
||||
cancellationMessage = _t("The homeserver doesn't support signing in another device.");
|
||||
break;
|
||||
default:
|
||||
cancellationMessage = _t("The request was cancelled.");
|
||||
break;
|
||||
}
|
||||
title = _t("Connection failed");
|
||||
centreTitle = true;
|
||||
titleIcon = <WarningBadge className="error" />;
|
||||
backButton = false;
|
||||
main = <p data-testid="cancellation-message">{ cancellationMessage }</p>;
|
||||
buttons = <>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={this.tryAgainClicked}
|
||||
>
|
||||
{ _t("Try again") }
|
||||
</AccessibleButton>
|
||||
{ this.cancelButton() }
|
||||
</>;
|
||||
break;
|
||||
case Phase.Connected:
|
||||
title = _t("Devices connected");
|
||||
titleIcon = <DevicesIcon className="normal" />;
|
||||
backButton = false;
|
||||
main = <>
|
||||
<p>{ _t("Check that the code below matches with your other device:") }</p>
|
||||
<div className="mx_LoginWithQR_confirmationDigits">
|
||||
{ this.state.confirmationDigits }
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_confirmationAlert">
|
||||
<div>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
<div>{ _t("By approving access for this device, it will have full access to your account.") }</div>
|
||||
</div>
|
||||
</>;
|
||||
|
||||
buttons = <>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.declineClicked}
|
||||
>
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="approve-login-button"
|
||||
kind="primary"
|
||||
onClick={this.approveLogin}
|
||||
>
|
||||
{ _t("Approve") }
|
||||
</AccessibleButton>
|
||||
</>;
|
||||
break;
|
||||
case Phase.ShowingQR:
|
||||
title =_t("Sign in with QR code");
|
||||
if (this.state.rendezvous) {
|
||||
const code = <div className="mx_LoginWithQR_qrWrapper">
|
||||
<QRCode data={[{ data: Buffer.from(this.state.rendezvous.code), mode: 'byte' }]} className="mx_QRCode" />
|
||||
</div>;
|
||||
main = <>
|
||||
<p>{ _t("Scan the QR code below with your device that's signed out.") }</p>
|
||||
<ol>
|
||||
<li>{ _t("Start at the sign in screen") }</li>
|
||||
<li>{ _t("Select 'Scan QR code'") }</li>
|
||||
<li>{ _t("Review and approve the sign in") }</li>
|
||||
</ol>
|
||||
{ code }
|
||||
</>;
|
||||
} else {
|
||||
main = this.simpleSpinner();
|
||||
buttons = this.cancelButton();
|
||||
}
|
||||
break;
|
||||
case Phase.Loading:
|
||||
main = this.simpleSpinner();
|
||||
break;
|
||||
case Phase.Connecting:
|
||||
main = this.simpleSpinner(_t("Connecting..."));
|
||||
buttons = this.cancelButton();
|
||||
break;
|
||||
case Phase.WaitingForDevice:
|
||||
main = this.simpleSpinner(_t("Waiting for device to sign in"));
|
||||
buttons = this.cancelButton();
|
||||
break;
|
||||
case Phase.Verifying:
|
||||
title = _t("Success");
|
||||
centreTitle = true;
|
||||
main = this.simpleSpinner(_t("Completing set up of your new device"));
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="login-with-qr" className="mx_LoginWithQR">
|
||||
<div className={centreTitle ? "mx_LoginWithQR_centreTitle" : ""}>
|
||||
{ backButton ?
|
||||
<AccessibleButton
|
||||
data-testid="back-button"
|
||||
className="mx_LoginWithQR_BackButton"
|
||||
onClick={this.onBackClick}
|
||||
title="Back"
|
||||
>
|
||||
<BackButtonIcon />
|
||||
</AccessibleButton>
|
||||
: null }
|
||||
<h1>{ titleIcon }{ title }</h1>
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_main">
|
||||
{ main }
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_buttons">
|
||||
{ buttons }
|
||||
</div>
|
||||
</div>
|
||||
<LoginWithQRFlow
|
||||
onClick={this.onClick}
|
||||
phase={this.state.phase}
|
||||
code={this.state.phase === Phase.ShowingQR ? this.state.rendezvous?.code : undefined}
|
||||
confirmationDigits={this.state.phase === Phase.Connected ? this.state.confirmationDigits : undefined}
|
||||
failureReason={this.state.phase === Phase.Error ? this.state.failureReason : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue