diff --git a/_locales/en/messages.json b/_locales/en/messages.json index faa4925a79ae..c415321b967f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2664,6 +2664,10 @@ "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" }, + "icu:Install__learn-more": { + "messageformat": "Learn more", + "description": "Button text for learn more button during error screen" + }, "icu:Install__scan-this-code": { "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" @@ -2701,7 +2705,11 @@ "description": "Instructions on the device link screen" }, "icu:Install__qr-failed": { - "messageformat": "The QR code couldn't load. Check your internet and try again. Learn more", + "messageformat": "(deleted 05/09/2023) The QR code couldn't load. Check your internet and try again. Learn more", + "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", "description": "Shown on the install screen if the QR code fails to load" }, "Install__support-link": { diff --git a/stylesheets/components/InstallScreenQrCodeNotScannedStep.scss b/stylesheets/components/InstallScreenQrCodeNotScannedStep.scss index 93f660961893..f9536329c1c1 100644 --- a/stylesheets/components/InstallScreenQrCodeNotScannedStep.scss +++ b/stylesheets/components/InstallScreenQrCodeNotScannedStep.scss @@ -55,6 +55,11 @@ color: $color-gray-60; } + &__link { + @include button-reset; + color: $color-ultramarine; + } + &__code { height: $size; width: $size; diff --git a/ts/components/installScreen/InstallScreenErrorStep.tsx b/ts/components/installScreen/InstallScreenErrorStep.tsx index de0eb5e027d2..a4edcdac74ce 100644 --- a/ts/components/installScreen/InstallScreenErrorStep.tsx +++ b/ts/components/installScreen/InstallScreenErrorStep.tsx @@ -9,12 +9,14 @@ import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser'; import { Button, ButtonVariant } from '../Button'; import { TitlebarDragArea } from '../TitlebarDragArea'; import { InstallScreenSignalLogo } from './InstallScreenSignalLogo'; +import { LINK_SIGNAL_DESKTOP } from '../../types/support'; export enum InstallError { TooManyDevices, TooOld, ConnectionFailed, UnknownError, + QRCodeFailed, } export function InstallScreenErrorStep({ @@ -51,6 +53,14 @@ export function InstallScreenErrorStep({ case InstallError.UnknownError: errorMessage = i18n('icu:installUnknownError'); break; + case InstallError.QRCodeFailed: + buttonText = i18n('icu:Install__learn-more'); + errorMessage = i18n('icu:installUnknownError'); + onClickButton = () => { + openLinkInWebBrowser(LINK_SIGNAL_DESKTOP); + }; + shouldShowQuitButton = true; + break; default: throw missingCaseError(error); } diff --git a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx index 93ab9efe1ca6..3610be4d7794 100644 --- a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx +++ b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx @@ -56,6 +56,7 @@ function Simulation({ finalResult }: { finalResult: Loadable }) { OS="macOS" startUpdate={action('startUpdate')} currentVersion="v6.0.0" + retryGetQrCode={action('retryGetQrCode')} /> ); } @@ -71,6 +72,7 @@ export function QrCodeLoading(): JSX.Element { OS="macOS" startUpdate={action('startUpdate')} currentVersion="v6.0.0" + retryGetQrCode={action('retryGetQrCode')} /> ); } @@ -91,6 +93,7 @@ export function QrCodeFailedToLoad(): JSX.Element { OS="macOS" startUpdate={action('startUpdate')} currentVersion="v6.0.0" + retryGetQrCode={action('retryGetQrCode')} /> ); } @@ -108,6 +111,7 @@ export function QrCodeLoaded(): JSX.Element { OS="macOS" startUpdate={action('startUpdate')} currentVersion="v6.0.0" + retryGetQrCode={action('retryGetQrCode')} /> ); } @@ -158,6 +162,7 @@ export function WithUpdateKnobs({ OS="macOS" startUpdate={action('startUpdate')} currentVersion={currentVersion} + retryGetQrCode={action('retryGetQrCode')} /> ); } diff --git a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx index b8dfd7d0cf01..519d602285a2 100644 --- a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx +++ b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx @@ -29,24 +29,23 @@ type PropsType = Readonly<{ updates: UpdatesStateType; currentVersion: string; OS: string; + retryGetQrCode: () => void; startUpdate: () => void; }>; -const QR_CODE_FAILED_LINK = - 'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device'; - const getQrCodeClassName = getClassNamesFor( 'module-InstallScreenQrCodeNotScannedStep__qr-code' ); export function InstallScreenQrCodeNotScannedStep({ - i18n, - provisioningUrl, - hasExpired, - updates, - startUpdate, currentVersion, + hasExpired, + i18n, OS, + provisioningUrl, + retryGetQrCode, + startUpdate, + updates, }: Readonly): ReactElement { return (
@@ -65,7 +64,11 @@ export function InstallScreenQrCodeNotScannedStep({ )}
- +

{i18n('icu:Install__scan-this-code')}

    @@ -110,7 +113,7 @@ export function InstallScreenQrCodeNotScannedStep({ } function InstallScreenQrCode( - props: Loadable & { i18n: LocalizerType } + props: Loadable & { i18n: LocalizerType; retryGetQrCode: () => void } ): ReactElement { const { i18n } = props; @@ -124,11 +127,24 @@ function InstallScreenQrCode( ( - {children} + retry: children => ( + ), }} /> diff --git a/ts/state/smart/InstallScreen.tsx b/ts/state/smart/InstallScreen.tsx index 9991e00b007b..5d6c8524968a 100644 --- a/ts/state/smart/InstallScreen.tsx +++ b/ts/state/smart/InstallScreen.tsx @@ -4,6 +4,7 @@ import type { ComponentProps, ReactElement } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; +import pTimeout, { TimeoutError } from 'p-timeout'; import { getIntl } from '../selectors/user'; import { getUpdatesState } from '../selectors/updates'; @@ -28,6 +29,9 @@ import { isRecord } from '../../util/isRecord'; import * as Errors from '../../types/errors'; import { normalizeDeviceName } from '../../util/normalizeDeviceName'; import OS from '../../util/os/osMain'; +import { SECOND } from '../../util/durations'; +import { BackOff } from '../../util/BackOff'; +import { drop } from '../../util/drop'; type PropsType = ComponentProps; @@ -53,6 +57,13 @@ const INITIAL_STATE: StateType = { provisioningUrl: { loadingState: LoadingState.Loading }, }; +const qrCodeBackOff = new BackOff([ + 10 * SECOND, + 20 * SECOND, + 30 * SECOND, + 60 * SECOND, +]); + function getInstallError(err: unknown): InstallError { if (err instanceof HTTPError) { switch (err.code) { @@ -83,6 +94,7 @@ export function SmartInstallScreen(): ReactElement { const chooseDeviceNamePromiseWrapperRef = useRef(explodePromise()); const [state, setState] = useState(INITIAL_STATE); + const [retryCounter, setRetryCounter] = useState(0); const setProvisioningUrl = useCallback( (value: string) => { @@ -206,12 +218,16 @@ export function SmartInstallScreen(): ReactElement { return result; }; - void (async () => { + async function getQRCode(): Promise { + const sleepError = new TimeoutError(); try { - await accountManager.registerSecondDevice( + const qrCodePromise = accountManager.registerSecondDevice( updateProvisioningUrl, confirmNumber ); + const sleepMs = qrCodeBackOff.getAndIncrement(); + log.info(`InstallScreen/getQRCode: race to ${sleepMs}ms`); + await pTimeout(qrCodePromise, sleepMs, sleepError); window.IPC.removeSetupMenuItems(); } catch (error) { @@ -222,17 +238,36 @@ export function SmartInstallScreen(): ReactElement { if (hasCleanedUp) { return; } - setState({ - step: InstallScreenStep.Error, - error: getInstallError(error), - }); + + 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({ + step: InstallScreenStep.Error, + error: getInstallError(error), + }); + } } - })(); + } + + drop(getQRCode()); return () => { hasCleanedUp = true; }; - }, [setProvisioningUrl, onQrCodeScanned]); + }, [setProvisioningUrl, retryCounter, onQrCodeScanned]); let props: PropsType; @@ -258,6 +293,10 @@ export function SmartInstallScreen(): ReactElement { updates, currentVersion: window.getVersion(), startUpdate, + retryGetQrCode: () => { + setRetryCounter(count => count + 1); + setState(INITIAL_STATE); + }, OS: OS.getName(), }, }; diff --git a/ts/types/support.ts b/ts/types/support.ts index 8e2fa41ca127..251fb37f7a5a 100644 --- a/ts/types/support.ts +++ b/ts/types/support.ts @@ -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 UNSUPPORTED_OS_URL = 'https://support.signal.org/hc/articles/5109141421850'; +export const LINK_SIGNAL_DESKTOP = + 'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';