Show error state when QR code times out loading

This commit is contained in:
Josh Perez 2023-05-10 17:36:45 -04:00 committed by GitHub
parent 48545d6a83
commit b34ea60d34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 22 deletions

View file

@ -2664,6 +2664,10 @@
"message": "Scan this code in the Signal app on your phone", "message": "Scan this code in the Signal app on your phone",
"description": "(deleted 03/29/2023) Title of the device link screen. Also used as alt text for the QR code on the device link screen" "description": "(deleted 03/29/2023) Title of the device link screen. Also used as alt text for the QR code on the device link screen"
}, },
"icu:Install__learn-more": {
"messageformat": "Learn more",
"description": "Button text for learn more button during error screen"
},
"icu:Install__scan-this-code": { "icu:Install__scan-this-code": {
"messageformat": "Scan this code in the Signal app on your phone", "messageformat": "Scan this code in the Signal app on your phone",
"description": "Title of the device link screen. Also used as alt text for the QR code on the device link screen" "description": "Title of the device link screen. Also used as alt text for the QR code on the device link screen"
@ -2701,7 +2705,11 @@
"description": "Instructions on the device link screen" "description": "Instructions on the device link screen"
}, },
"icu:Install__qr-failed": { "icu:Install__qr-failed": {
"messageformat": "The QR code couldn't load. Check your internet and try again. <learnMoreLink>Learn more</learnMoreLink>", "messageformat": "(deleted 05/09/2023) The QR code couldn't load. Check your internet and try again. <learnMoreLink>Learn more</learnMoreLink>",
"description": "Shown on the install screen if the QR code fails to load"
},
"icu:Install__qr-failed-load": {
"messageformat": "The QR code couldn't load. Check your internet and try again. <retry>Retry</retry>",
"description": "Shown on the install screen if the QR code fails to load" "description": "Shown on the install screen if the QR code fails to load"
}, },
"Install__support-link": { "Install__support-link": {

View file

@ -55,6 +55,11 @@
color: $color-gray-60; color: $color-gray-60;
} }
&__link {
@include button-reset;
color: $color-ultramarine;
}
&__code { &__code {
height: $size; height: $size;
width: $size; width: $size;

View file

@ -9,12 +9,14 @@ import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser';
import { Button, ButtonVariant } from '../Button'; import { Button, ButtonVariant } from '../Button';
import { TitlebarDragArea } from '../TitlebarDragArea'; import { TitlebarDragArea } from '../TitlebarDragArea';
import { InstallScreenSignalLogo } from './InstallScreenSignalLogo'; import { InstallScreenSignalLogo } from './InstallScreenSignalLogo';
import { LINK_SIGNAL_DESKTOP } from '../../types/support';
export enum InstallError { export enum InstallError {
TooManyDevices, TooManyDevices,
TooOld, TooOld,
ConnectionFailed, ConnectionFailed,
UnknownError, UnknownError,
QRCodeFailed,
} }
export function InstallScreenErrorStep({ export function InstallScreenErrorStep({
@ -51,6 +53,14 @@ export function InstallScreenErrorStep({
case InstallError.UnknownError: case InstallError.UnknownError:
errorMessage = i18n('icu:installUnknownError'); errorMessage = i18n('icu:installUnknownError');
break; break;
case InstallError.QRCodeFailed:
buttonText = i18n('icu:Install__learn-more');
errorMessage = i18n('icu:installUnknownError');
onClickButton = () => {
openLinkInWebBrowser(LINK_SIGNAL_DESKTOP);
};
shouldShowQuitButton = true;
break;
default: default:
throw missingCaseError(error); throw missingCaseError(error);
} }

View file

@ -56,6 +56,7 @@ function Simulation({ finalResult }: { finalResult: Loadable<string> }) {
OS="macOS" OS="macOS"
startUpdate={action('startUpdate')} startUpdate={action('startUpdate')}
currentVersion="v6.0.0" currentVersion="v6.0.0"
retryGetQrCode={action('retryGetQrCode')}
/> />
); );
} }
@ -71,6 +72,7 @@ export function QrCodeLoading(): JSX.Element {
OS="macOS" OS="macOS"
startUpdate={action('startUpdate')} startUpdate={action('startUpdate')}
currentVersion="v6.0.0" currentVersion="v6.0.0"
retryGetQrCode={action('retryGetQrCode')}
/> />
); );
} }
@ -91,6 +93,7 @@ export function QrCodeFailedToLoad(): JSX.Element {
OS="macOS" OS="macOS"
startUpdate={action('startUpdate')} startUpdate={action('startUpdate')}
currentVersion="v6.0.0" currentVersion="v6.0.0"
retryGetQrCode={action('retryGetQrCode')}
/> />
); );
} }
@ -108,6 +111,7 @@ export function QrCodeLoaded(): JSX.Element {
OS="macOS" OS="macOS"
startUpdate={action('startUpdate')} startUpdate={action('startUpdate')}
currentVersion="v6.0.0" currentVersion="v6.0.0"
retryGetQrCode={action('retryGetQrCode')}
/> />
); );
} }
@ -158,6 +162,7 @@ export function WithUpdateKnobs({
OS="macOS" OS="macOS"
startUpdate={action('startUpdate')} startUpdate={action('startUpdate')}
currentVersion={currentVersion} currentVersion={currentVersion}
retryGetQrCode={action('retryGetQrCode')}
/> />
); );
} }

View file

@ -29,24 +29,23 @@ type PropsType = Readonly<{
updates: UpdatesStateType; updates: UpdatesStateType;
currentVersion: string; currentVersion: string;
OS: string; OS: string;
retryGetQrCode: () => void;
startUpdate: () => void; startUpdate: () => void;
}>; }>;
const QR_CODE_FAILED_LINK =
'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';
const getQrCodeClassName = getClassNamesFor( const getQrCodeClassName = getClassNamesFor(
'module-InstallScreenQrCodeNotScannedStep__qr-code' 'module-InstallScreenQrCodeNotScannedStep__qr-code'
); );
export function InstallScreenQrCodeNotScannedStep({ export function InstallScreenQrCodeNotScannedStep({
i18n,
provisioningUrl,
hasExpired,
updates,
startUpdate,
currentVersion, currentVersion,
hasExpired,
i18n,
OS, OS,
provisioningUrl,
retryGetQrCode,
startUpdate,
updates,
}: Readonly<PropsType>): ReactElement { }: Readonly<PropsType>): ReactElement {
return ( return (
<div className="module-InstallScreenQrCodeNotScannedStep"> <div className="module-InstallScreenQrCodeNotScannedStep">
@ -65,7 +64,11 @@ export function InstallScreenQrCodeNotScannedStep({
)} )}
<div className="module-InstallScreenQrCodeNotScannedStep__contents"> <div className="module-InstallScreenQrCodeNotScannedStep__contents">
<InstallScreenQrCode i18n={i18n} {...provisioningUrl} /> <InstallScreenQrCode
i18n={i18n}
{...provisioningUrl}
retryGetQrCode={retryGetQrCode}
/>
<div className="module-InstallScreenQrCodeNotScannedStep__instructions"> <div className="module-InstallScreenQrCodeNotScannedStep__instructions">
<h1>{i18n('icu:Install__scan-this-code')}</h1> <h1>{i18n('icu:Install__scan-this-code')}</h1>
<ol> <ol>
@ -110,7 +113,7 @@ export function InstallScreenQrCodeNotScannedStep({
} }
function InstallScreenQrCode( function InstallScreenQrCode(
props: Loadable<string> & { i18n: LocalizerType } props: Loadable<string> & { i18n: LocalizerType; retryGetQrCode: () => void }
): ReactElement { ): ReactElement {
const { i18n } = props; const { i18n } = props;
@ -124,11 +127,24 @@ function InstallScreenQrCode(
<span className={classNames(getQrCodeClassName('__error-message'))}> <span className={classNames(getQrCodeClassName('__error-message'))}>
<Intl <Intl
i18n={i18n} i18n={i18n}
id="icu:Install__qr-failed" id="icu:Install__qr-failed-load"
components={{ components={{
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
learnMoreLink: children => ( retry: children => (
<a href={QR_CODE_FAILED_LINK}>{children}</a> <button
className={getQrCodeClassName('__link')}
onClick={props.retryGetQrCode}
onKeyDown={ev => {
if (ev.key === 'Enter') {
props.retryGetQrCode();
ev.preventDefault();
ev.stopPropagation();
}
}}
type="button"
>
{children}
</button>
), ),
}} }}
/> />

View file

@ -4,6 +4,7 @@
import type { ComponentProps, ReactElement } from 'react'; import type { ComponentProps, ReactElement } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import pTimeout, { TimeoutError } from 'p-timeout';
import { getIntl } from '../selectors/user'; import { getIntl } from '../selectors/user';
import { getUpdatesState } from '../selectors/updates'; import { getUpdatesState } from '../selectors/updates';
@ -28,6 +29,9 @@ import { isRecord } from '../../util/isRecord';
import * as Errors from '../../types/errors'; import * as Errors from '../../types/errors';
import { normalizeDeviceName } from '../../util/normalizeDeviceName'; import { normalizeDeviceName } from '../../util/normalizeDeviceName';
import OS from '../../util/os/osMain'; import OS from '../../util/os/osMain';
import { SECOND } from '../../util/durations';
import { BackOff } from '../../util/BackOff';
import { drop } from '../../util/drop';
type PropsType = ComponentProps<typeof InstallScreen>; type PropsType = ComponentProps<typeof InstallScreen>;
@ -53,6 +57,13 @@ const INITIAL_STATE: StateType = {
provisioningUrl: { loadingState: LoadingState.Loading }, provisioningUrl: { loadingState: LoadingState.Loading },
}; };
const qrCodeBackOff = new BackOff([
10 * SECOND,
20 * SECOND,
30 * SECOND,
60 * SECOND,
]);
function getInstallError(err: unknown): InstallError { function getInstallError(err: unknown): InstallError {
if (err instanceof HTTPError) { if (err instanceof HTTPError) {
switch (err.code) { switch (err.code) {
@ -83,6 +94,7 @@ export function SmartInstallScreen(): ReactElement {
const chooseDeviceNamePromiseWrapperRef = useRef(explodePromise<string>()); const chooseDeviceNamePromiseWrapperRef = useRef(explodePromise<string>());
const [state, setState] = useState<StateType>(INITIAL_STATE); const [state, setState] = useState<StateType>(INITIAL_STATE);
const [retryCounter, setRetryCounter] = useState(0);
const setProvisioningUrl = useCallback( const setProvisioningUrl = useCallback(
(value: string) => { (value: string) => {
@ -206,12 +218,16 @@ export function SmartInstallScreen(): ReactElement {
return result; return result;
}; };
void (async () => { async function getQRCode(): Promise<void> {
const sleepError = new TimeoutError();
try { try {
await accountManager.registerSecondDevice( const qrCodePromise = accountManager.registerSecondDevice(
updateProvisioningUrl, updateProvisioningUrl,
confirmNumber confirmNumber
); );
const sleepMs = qrCodeBackOff.getAndIncrement();
log.info(`InstallScreen/getQRCode: race to ${sleepMs}ms`);
await pTimeout(qrCodePromise, sleepMs, sleepError);
window.IPC.removeSetupMenuItems(); window.IPC.removeSetupMenuItems();
} catch (error) { } catch (error) {
@ -222,17 +238,36 @@ export function SmartInstallScreen(): ReactElement {
if (hasCleanedUp) { if (hasCleanedUp) {
return; return;
} }
if (qrCodeBackOff.isFull()) {
log.error('InstallScreen/getQRCode: too many tries');
setState({
step: InstallScreenStep.Error,
error: InstallError.QRCodeFailed,
});
return;
}
if (error === sleepError) {
setState({
step: InstallScreenStep.QrCodeNotScanned,
provisioningUrl: { loadingState: LoadingState.LoadFailed, error },
});
} else {
setState({ setState({
step: InstallScreenStep.Error, step: InstallScreenStep.Error,
error: getInstallError(error), error: getInstallError(error),
}); });
} }
})(); }
}
drop(getQRCode());
return () => { return () => {
hasCleanedUp = true; hasCleanedUp = true;
}; };
}, [setProvisioningUrl, onQrCodeScanned]); }, [setProvisioningUrl, retryCounter, onQrCodeScanned]);
let props: PropsType; let props: PropsType;
@ -258,6 +293,10 @@ export function SmartInstallScreen(): ReactElement {
updates, updates,
currentVersion: window.getVersion(), currentVersion: window.getVersion(),
startUpdate, startUpdate,
retryGetQrCode: () => {
setRetryCounter(count => count + 1);
setState(INITIAL_STATE);
},
OS: OS.getName(), OS: OS.getName(),
}, },
}; };

View file

@ -5,3 +5,5 @@ export const PRODUCTION_DOWNLOAD_URL = 'https://signal.org/download/';
export const BETA_DOWNLOAD_URL = 'https://support.signal.org/beta'; export const BETA_DOWNLOAD_URL = 'https://support.signal.org/beta';
export const UNSUPPORTED_OS_URL = export const UNSUPPORTED_OS_URL =
'https://support.signal.org/hc/articles/5109141421850'; 'https://support.signal.org/hc/articles/5109141421850';
export const LINK_SIGNAL_DESKTOP =
'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';