Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32177875
D15163.1765070058.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
D15163.1765070058.diff
View Options
diff --git a/web/account/restoration.css b/web/account/restoration.css
--- a/web/account/restoration.css
+++ b/web/account/restoration.css
@@ -177,6 +177,7 @@
flex-direction: column;
align-items: stretch;
row-gap: 8px;
+ margin-top: auto;
}
.errorButtons button {
diff --git a/web/account/restoration.react.js b/web/account/restoration.react.js
--- a/web/account/restoration.react.js
+++ b/web/account/restoration.react.js
@@ -10,8 +10,12 @@
logOutActionTypes,
useSecondaryDeviceLogOut,
} from 'lib/actions/user-actions.js';
+import { useSecondaryDeviceQRAuthContext } from 'lib/components/secondary-device-qr-auth-context-provider.react.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
-import { getMessageForException } from 'lib/utils/errors.js';
+import {
+ BackupIsNewerError,
+ getMessageForException,
+} from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
@@ -21,6 +25,10 @@
import LoadingIndicator from '../loading-indicator.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { useStaffCanSee } from '../utils/staff-utils.js';
+import {
+ getBackupIsNewerThanAppError,
+ getVersionUnsupportedError,
+} from '../utils/version-utils.js';
type RestorationStep = 'authenticating' | 'restoring';
@@ -118,13 +126,13 @@
);
}
-type RestorationErrorProps = {
+type FailedRestorationErrorProps = {
+error: Error,
- +step: RestorationStep,
};
-
-function RestorationError(props: RestorationErrorProps): React.Node {
- const { step, error } = props;
+function RestorationFailedError(
+ props: FailedRestorationErrorProps,
+): React.Node {
+ const { error } = props;
const dispatch = useDispatch();
const dispatchActionPromise = useDispatchActionPromise();
@@ -155,7 +163,7 @@
return (
<RestorationViewContainer
title="Restoration failed"
- step={step}
+ step="restoring"
isError={true}
>
<div className={css.errorMessageContainer}>
@@ -193,38 +201,165 @@
);
}
-export type RestorationProgressProps = {
+type SimpleErrorProps = {
+ +title: string,
+ +rawErrorMessage?: ?string,
+ +step: RestorationStep,
+ +onTryAgain?: () => void,
+ +children?: React.Node,
+};
+function SimpleError(props: SimpleErrorProps): React.Node {
+ const { title, step, children, rawErrorMessage, onTryAgain } = props;
+
+ return (
+ <RestorationViewContainer title={title} step={step} isError={true}>
+ <div className={css.errorMessageContainer}>
+ <div className={css.modalText}>{children}</div>
+ {rawErrorMessage && (
+ <div className={css.errorDetailsContainer}>
+ <div className={css.errorDetailsHeader}>Error message:</div>
+ <div className={css.errorDetails}>{rawErrorMessage}</div>
+ </div>
+ )}
+ </div>
+ <div className={css.errorButtons}>
+ {!!onTryAgain && (
+ <Button
+ variant="filled"
+ buttonColor={buttonThemes.primary}
+ onClick={onTryAgain}
+ >
+ Try again
+ </Button>
+ )}
+ </div>
+ </RestorationViewContainer>
+ );
+}
+
+export type ProgressViewProps = {
+ +step: RestorationStep,
+};
+function ProgressView(props: ProgressViewProps): React.Node {
+ const { step } = props;
+
+ const title =
+ step === 'authenticating' ? 'Authenticating device' : 'Restoring your data';
+
+ return (
+ <RestorationViewContainer title={title} step={step} isError={false}>
+ <div className={css.restorationSpinnerWrapper}>
+ <LoadingIndicator status="loading" size="x-large" />
+ </div>
+ </RestorationViewContainer>
+ );
+}
+
+type AuthErrorProps = {
+ +error: mixed,
+ +handleRetry: () => void,
+};
+function AuthErrorView(props: AuthErrorProps): React.Node {
+ const { error, handleRetry } = props;
+
+ const rawErrorMessage = React.useMemo(
+ () => getMessageForException(error),
+ [error],
+ );
+
+ if (
+ rawErrorMessage === 'client_version_unsupported' ||
+ rawErrorMessage === 'unsupported_version'
+ ) {
+ return (
+ <SimpleError title="App version unsupported" step="authenticating">
+ {getVersionUnsupportedError()}
+ </SimpleError>
+ );
+ } else if (rawErrorMessage === 'network_error') {
+ return (
+ <SimpleError
+ title="Network error"
+ step="authenticating"
+ onTryAgain={handleRetry}
+ >
+ Failed to contact Comm services. Please check your network connection.
+ </SimpleError>
+ );
+ } else {
+ return (
+ <SimpleError
+ title="Unknown error"
+ step="authenticating"
+ onTryAgain={handleRetry}
+ rawErrorMessage={rawErrorMessage}
+ >
+ Uhh... try again?
+ </SimpleError>
+ );
+ }
+}
+
+type RestoreErrorProps = {
+ +error: Error,
+};
+function RestoreErrorView(props: RestoreErrorProps): React.Node {
+ const { error } = props;
+
+ if (error instanceof BackupIsNewerError) {
+ return (
+ <SimpleError step="restoring" title="App out of date">
+ {getBackupIsNewerThanAppError()}
+ </SimpleError>
+ );
+ } else {
+ return <RestorationFailedError error={error} />;
+ }
+}
+
+export type RestorationViewProps = {
+qrAuthInProgress: boolean,
+userDataRestoreStarted: boolean,
};
-
-function RestorationProgress(props: RestorationProgressProps): React.Node {
+function RestorationView(props: RestorationViewProps): React.Node {
const { qrAuthInProgress, userDataRestoreStarted } = props;
- const restorationError = useSelector(state =>
+ const { registerErrorListener } = useSecondaryDeviceQRAuthContext();
+ const [qrAuthError, setQRAuthError] = React.useState<?mixed>(null);
+ const userDataError = useSelector(state =>
state.restoreBackupState.status === 'user_data_restore_failed'
? state.restoreBackupState.payload.error
: null,
);
+ React.useEffect(() => {
+ const subscription = registerErrorListener((error, isUserDataError) => {
+ if (isUserDataError) {
+ // user data errors are handled by selector
+ return;
+ }
+ setQRAuthError(error);
+ });
+
+ return () => subscription.remove();
+ }, [registerErrorListener]);
+
+ const retryQRAuth = React.useCallback(() => {
+ setQRAuthError(null);
+ }, []);
+
+ if (userDataError) {
+ return <RestoreErrorView error={userDataError} />;
+ } else if (qrAuthError) {
+ return <AuthErrorView error={qrAuthError} handleRetry={retryQRAuth} />;
+ }
+
const step: RestorationStep =
qrAuthInProgress && !userDataRestoreStarted
? 'authenticating'
: 'restoring';
- if (restorationError) {
- return <RestorationError step={step} error={restorationError} />;
- }
-
- const title =
- step === 'authenticating' ? 'Authenticating device' : 'Restoring your data';
- return (
- <RestorationViewContainer title={title} step={step} isError={false}>
- <div className={css.restorationSpinnerWrapper}>
- <LoadingIndicator status="loading" size="x-large" />
- </div>
- </RestorationViewContainer>
- );
+ return <ProgressView step={step} />;
}
-export default RestorationProgress;
+export default RestorationView;
diff --git a/web/utils/qr-code-utils.js b/web/utils/qr-code-utils.js
--- a/web/utils/qr-code-utils.js
+++ b/web/utils/qr-code-utils.js
@@ -22,6 +22,7 @@
BackupIsNewerError,
getMessageForException,
} from 'lib/utils/errors.js';
+import { fullBackupSupport } from 'lib/utils/services-utils.js';
import { base64DecodeBuffer, base64EncodeBuffer } from './base64-utils.js';
import { getBackupIsNewerThanAppError } from './version-utils.js';
@@ -68,6 +69,11 @@
return React.useCallback(
(error: mixed, isUserDataRestoreError?: boolean) => {
console.error('Secondary device log in error:', error);
+ if (fullBackupSupport) {
+ // for full backup, errors are handled in the restore UI
+ return;
+ }
+
const messageForException = getMessageForException(error);
if (
messageForException === 'client_version_unsupported' ||
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 7, 1:14 AM (2 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5841920
Default Alt Text
D15163.1765070058.diff (8 KB)
Attached To
Mode
D15163: [web] Handle QR auth errors in restore UI
Attached
Detach File
Event Timeline
Log In to Comment