mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-19 17:58:48 +00:00
Fix link-and-sync cancellation
This commit is contained in:
@@ -114,8 +114,8 @@
|
|||||||
"@indutny/simple-windows-notifications": "2.0.16",
|
"@indutny/simple-windows-notifications": "2.0.16",
|
||||||
"@indutny/sneequals": "4.0.0",
|
"@indutny/sneequals": "4.0.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@react-aria/interactions": "3.23.0",
|
|
||||||
"@react-aria/focus": "3.19.1",
|
"@react-aria/focus": "3.19.1",
|
||||||
|
"@react-aria/interactions": "3.23.0",
|
||||||
"@react-aria/utils": "3.25.3",
|
"@react-aria/utils": "3.25.3",
|
||||||
"@react-spring/web": "9.7.5",
|
"@react-spring/web": "9.7.5",
|
||||||
"@signalapp/libsignal-client": "0.68.0",
|
"@signalapp/libsignal-client": "0.68.0",
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
"@indutny/parallel-prettier": "3.0.0",
|
"@indutny/parallel-prettier": "3.0.0",
|
||||||
"@indutny/rezip-electron": "2.0.1",
|
"@indutny/rezip-electron": "2.0.1",
|
||||||
"@napi-rs/canvas": "0.1.61",
|
"@napi-rs/canvas": "0.1.61",
|
||||||
"@signalapp/mock-server": "11.2.0",
|
"@signalapp/mock-server": "11.3.0",
|
||||||
"@storybook/addon-a11y": "8.4.4",
|
"@storybook/addon-a11y": "8.4.4",
|
||||||
"@storybook/addon-actions": "8.4.4",
|
"@storybook/addon-actions": "8.4.4",
|
||||||
"@storybook/addon-controls": "8.4.4",
|
"@storybook/addon-controls": "8.4.4",
|
||||||
|
|||||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -430,8 +430,8 @@ importers:
|
|||||||
specifier: 0.1.61
|
specifier: 0.1.61
|
||||||
version: 0.1.61
|
version: 0.1.61
|
||||||
'@signalapp/mock-server':
|
'@signalapp/mock-server':
|
||||||
specifier: 11.2.0
|
specifier: 11.3.0
|
||||||
version: 11.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
version: 11.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
'@storybook/addon-a11y':
|
'@storybook/addon-a11y':
|
||||||
specifier: 8.4.4
|
specifier: 8.4.4
|
||||||
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
||||||
@@ -2531,8 +2531,8 @@ packages:
|
|||||||
'@signalapp/libsignal-client@0.68.0':
|
'@signalapp/libsignal-client@0.68.0':
|
||||||
resolution: {integrity: sha512-k7kUqN36wYMnx1ARVVpNmWJfVlD0AIrNEq0Mpb7X8yMc6E8QY5ankwtPX3ZlO/Yl7en2NT7ZrP4dM5xpQlGsNA==}
|
resolution: {integrity: sha512-k7kUqN36wYMnx1ARVVpNmWJfVlD0AIrNEq0Mpb7X8yMc6E8QY5ankwtPX3ZlO/Yl7en2NT7ZrP4dM5xpQlGsNA==}
|
||||||
|
|
||||||
'@signalapp/mock-server@11.2.0':
|
'@signalapp/mock-server@11.3.0':
|
||||||
resolution: {integrity: sha512-y8bueRcXVulyXRRVm2M/qT7YmxGpUbiwQsRSi7a+DDI4aUeZIDW9z7KgjElv1CN1/n9O6M1bYO+TLy4ys+7U6w==}
|
resolution: {integrity: sha512-X/yqrDySJ51bRngjMJrIGDhOU/LQ7OI9vJFNrs0835bgdBPGQO1pyufndzKLJpPehMECzGIP0UCE9YZU1X6NIg==}
|
||||||
|
|
||||||
'@signalapp/parchment-cjs@3.0.1':
|
'@signalapp/parchment-cjs@3.0.1':
|
||||||
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
||||||
@@ -5916,8 +5916,8 @@ packages:
|
|||||||
intl-tel-input@24.7.0:
|
intl-tel-input@24.7.0:
|
||||||
resolution: {integrity: sha512-OjkhKen4SJUI2kN9OHpb8ReNN619sB9gECPq51dn3zKEWvif3mnSjmrtWhm8ABIb7Ijs+AAYSS5sI33Sb4YqvQ==}
|
resolution: {integrity: sha512-OjkhKen4SJUI2kN9OHpb8ReNN619sB9gECPq51dn3zKEWvif3mnSjmrtWhm8ABIb7Ijs+AAYSS5sI33Sb4YqvQ==}
|
||||||
|
|
||||||
ioredis@5.5.0:
|
ioredis@5.6.0:
|
||||||
resolution: {integrity: sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==}
|
resolution: {integrity: sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
ip-address@9.0.5:
|
ip-address@9.0.5:
|
||||||
@@ -12110,7 +12110,7 @@ snapshots:
|
|||||||
type-fest: 4.26.1
|
type-fest: 4.26.1
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
|
|
||||||
'@signalapp/mock-server@11.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
'@signalapp/mock-server@11.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
||||||
'@signalapp/libsignal-client': 0.60.2
|
'@signalapp/libsignal-client': 0.60.2
|
||||||
@@ -12615,7 +12615,7 @@ snapshots:
|
|||||||
lodash.throttle: 4.1.1
|
lodash.throttle: 4.1.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@redis/client': 1.6.0
|
'@redis/client': 1.6.0
|
||||||
ioredis: 5.5.0
|
ioredis: 5.6.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -16316,7 +16316,7 @@ snapshots:
|
|||||||
|
|
||||||
intl-tel-input@24.7.0: {}
|
intl-tel-input@24.7.0: {}
|
||||||
|
|
||||||
ioredis@5.5.0:
|
ioredis@5.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ioredis/commands': 1.2.0
|
'@ioredis/commands': 1.2.0
|
||||||
cluster-key-slot: 1.1.2
|
cluster-key-slot: 1.1.2
|
||||||
|
|||||||
@@ -1773,6 +1773,7 @@ export async function startApp(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
log.info(`${logId}: waiting for postRegistrationSyncs`);
|
||||||
await Promise.all(syncsToAwaitBeforeShowingInbox);
|
await Promise.all(syncsToAwaitBeforeShowingInbox);
|
||||||
await window.storage.put('postRegistrationSyncsStatus', 'complete');
|
await window.storage.put('postRegistrationSyncsStatus', 'complete');
|
||||||
log.info(`${logId}: postRegistrationSyncs complete`);
|
log.info(`${logId}: postRegistrationSyncs complete`);
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export function FullFlow(): JSX.Element {
|
|||||||
currentBytes={currentBytes}
|
currentBytes={currentBytes}
|
||||||
totalBytes={totalBytes}
|
totalBytes={totalBytes}
|
||||||
backupStep={backupStep}
|
backupStep={backupStep}
|
||||||
onRestartLink={action('onRestartLink')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export type PropsType = Readonly<
|
|||||||
error?: InstallScreenBackupError;
|
error?: InstallScreenBackupError;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onRetry: () => void;
|
onRetry: () => void;
|
||||||
onRestartLink: () => void;
|
|
||||||
|
|
||||||
// Updater UI
|
// Updater UI
|
||||||
updates: UpdatesStateType;
|
updates: UpdatesStateType;
|
||||||
@@ -60,7 +59,6 @@ export function InstallScreenBackupImportStep(props: PropsType): JSX.Element {
|
|||||||
error,
|
error,
|
||||||
onCancel,
|
onCancel,
|
||||||
onRetry,
|
onRetry,
|
||||||
onRestartLink,
|
|
||||||
updates,
|
updates,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
OS,
|
OS,
|
||||||
@@ -140,7 +138,7 @@ export function InstallScreenBackupImportStep(props: PropsType): JSX.Element {
|
|||||||
title={i18n('icu:BackupImportScreen__error__title')}
|
title={i18n('icu:BackupImportScreen__error__title')}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
action: onRestartLink,
|
action: onCancel,
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
text: i18n('icu:BackupImportScreen__error__confirm'),
|
text: i18n('icu:BackupImportScreen__error__confirm'),
|
||||||
},
|
},
|
||||||
@@ -254,6 +252,21 @@ type ProgressBarPropsType = Readonly<
|
|||||||
|
|
||||||
function ProgressBarAndDescription(props: ProgressBarPropsType): JSX.Element {
|
function ProgressBarAndDescription(props: ProgressBarPropsType): JSX.Element {
|
||||||
const { backupStep, i18n, isCanceled } = props;
|
const { backupStep, i18n, isCanceled } = props;
|
||||||
|
|
||||||
|
if (isCanceled) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProgressBar
|
||||||
|
fractionComplete={null}
|
||||||
|
isRTL={i18n.getLocaleDirection() === 'rtl'}
|
||||||
|
/>
|
||||||
|
<div className="InstallScreenBackupImportStep__progressbar-hint">
|
||||||
|
{i18n('icu:BackupImportScreen__progressbar-hint--canceling')}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (backupStep === InstallScreenBackupStep.WaitForBackup) {
|
if (backupStep === InstallScreenBackupStep.WaitForBackup) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -274,20 +287,6 @@ function ProgressBarAndDescription(props: ProgressBarPropsType): JSX.Element {
|
|||||||
currentBytes / totalBytes
|
currentBytes / totalBytes
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isCanceled) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ProgressBar
|
|
||||||
fractionComplete={fractionComplete}
|
|
||||||
isRTL={i18n.getLocaleDirection() === 'rtl'}
|
|
||||||
/>
|
|
||||||
<div className="InstallScreenBackupImportStep__progressbar-hint">
|
|
||||||
{i18n('icu:BackupImportScreen__progressbar-hint--canceling')}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backupStep === InstallScreenBackupStep.Download) {
|
if (backupStep === InstallScreenBackupStep.Download) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -4,16 +4,48 @@
|
|||||||
|
|
||||||
import type Long from 'long';
|
import type Long from 'long';
|
||||||
|
|
||||||
export class UnsupportedBackupVersion extends Error {
|
import { InstallScreenBackupError } from '../../types/InstallScreen';
|
||||||
constructor(version: Long) {
|
|
||||||
super(`Unsupported backup version: ${version}`);
|
export class BackupInstallerError extends Error {
|
||||||
|
constructor(
|
||||||
|
name: string,
|
||||||
|
public readonly installerError: InstallScreenBackupError
|
||||||
|
) {
|
||||||
|
super(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BackupDownloadFailedError extends Error {}
|
export class UnsupportedBackupVersion extends BackupInstallerError {
|
||||||
|
constructor(version: Long) {
|
||||||
|
super(
|
||||||
|
`Unsupported backup version: ${version}`,
|
||||||
|
InstallScreenBackupError.UnsupportedVersion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BackupProcessingError extends Error {}
|
export class BackupDownloadFailedError extends BackupInstallerError {
|
||||||
|
constructor() {
|
||||||
|
super('BackupDownloadFailedError', InstallScreenBackupError.Retriable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BackupImportCanceledError extends Error {}
|
export class BackupProcessingError extends BackupInstallerError {
|
||||||
|
constructor(cause: Error) {
|
||||||
|
super('BackupProcessingError', InstallScreenBackupError.Fatal);
|
||||||
|
|
||||||
export class RelinkRequestedError extends Error {}
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BackupImportCanceledError extends BackupInstallerError {
|
||||||
|
constructor() {
|
||||||
|
super('BackupImportCanceledError', InstallScreenBackupError.Canceled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RelinkRequestedError extends BackupInstallerError {
|
||||||
|
constructor() {
|
||||||
|
super('RelinkRequestedError', InstallScreenBackupError.Fatal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ import { BackupAPI } from './api';
|
|||||||
import { validateBackup } from './validator';
|
import { validateBackup } from './validator';
|
||||||
import { BackupType } from './types';
|
import { BackupType } from './types';
|
||||||
import {
|
import {
|
||||||
|
BackupInstallerError,
|
||||||
BackupDownloadFailedError,
|
BackupDownloadFailedError,
|
||||||
BackupImportCanceledError,
|
BackupImportCanceledError,
|
||||||
BackupProcessingError,
|
BackupProcessingError,
|
||||||
RelinkRequestedError,
|
RelinkRequestedError,
|
||||||
UnsupportedBackupVersion,
|
|
||||||
} from './errors';
|
} from './errors';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
import { isAdhoc, isNightly } from '../../util/version';
|
import { isAdhoc, isNightly } from '../../util/version';
|
||||||
@@ -164,43 +164,22 @@ export class BackupsService {
|
|||||||
onProgress: options.onProgress,
|
onProgress: options.onProgress,
|
||||||
ephemeralKey,
|
ephemeralKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!hasBackup) {
|
||||||
|
// If the primary cancels sync on their end, then we can link without sync
|
||||||
|
log.info('backups.downloadAndImport: missing backup');
|
||||||
|
window.reduxActions.installer.handleMissingBackup();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.#downloadRetryPromise = explodePromise<RetryBackupImportValue>();
|
this.#downloadRetryPromise = explodePromise<RetryBackupImportValue>();
|
||||||
|
|
||||||
let installerError: InstallScreenBackupError;
|
let installerError: InstallScreenBackupError;
|
||||||
let shouldUnlinkAndDeleteData = false;
|
if (error instanceof BackupInstallerError) {
|
||||||
if (error instanceof RelinkRequestedError) {
|
|
||||||
installerError = InstallScreenBackupError.Fatal;
|
|
||||||
log.error(
|
log.error(
|
||||||
'backups.downloadAndImport: primary requested relink; unlinking & deleting data',
|
'backups.downloadAndImport: got installer error',
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
shouldUnlinkAndDeleteData = true;
|
({ installerError } = error);
|
||||||
} else if (error instanceof UnsupportedBackupVersion) {
|
|
||||||
installerError = InstallScreenBackupError.UnsupportedVersion;
|
|
||||||
log.error(
|
|
||||||
'backups.downloadAndImport: unsupported version',
|
|
||||||
Errors.toLogFormat(error)
|
|
||||||
);
|
|
||||||
} else if (error instanceof BackupDownloadFailedError) {
|
|
||||||
installerError = InstallScreenBackupError.Retriable;
|
|
||||||
log.warn(
|
|
||||||
'backups.downloadAndImport: download error, prompting user to retry',
|
|
||||||
Errors.toLogFormat(error)
|
|
||||||
);
|
|
||||||
} else if (error instanceof BackupProcessingError) {
|
|
||||||
installerError = InstallScreenBackupError.Fatal;
|
|
||||||
log.error(
|
|
||||||
'backups.downloadAndImport: fatal error during processing; unlinking & deleting data',
|
|
||||||
Errors.toLogFormat(error)
|
|
||||||
);
|
|
||||||
shouldUnlinkAndDeleteData = true;
|
|
||||||
} else if (error instanceof BackupImportCanceledError) {
|
|
||||||
installerError = InstallScreenBackupError.Canceled;
|
|
||||||
log.info(
|
|
||||||
'backups.downloadAndImport: Processing canceled by user; unlinking & deleting data'
|
|
||||||
);
|
|
||||||
shouldUnlinkAndDeleteData = true;
|
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
'backups.downloadAndImport: unknown error, prompting user to retry'
|
'backups.downloadAndImport: unknown error, prompting user to retry'
|
||||||
@@ -212,28 +191,38 @@ export class BackupsService {
|
|||||||
error: installerError,
|
error: installerError,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deleting data takes some time
|
// For download errors, wait for user confirmation to retry or unlink
|
||||||
if (shouldUnlinkAndDeleteData) {
|
const nextStep =
|
||||||
// eslint-disable-next-line no-await-in-loop
|
error instanceof BackupImportCanceledError
|
||||||
await this.#unlinkAndDeleteAllData();
|
? 'cancel'
|
||||||
|
: // eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.#downloadRetryPromise.promise;
|
||||||
|
if (nextStep === 'retry') {
|
||||||
|
log.warn('backups.downloadAndImport: retrying');
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For download errors, wait for user confirmation to retry or unlink
|
if (nextStep !== 'cancel') {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
throw missingCaseError(nextStep);
|
||||||
const nextStep = await this.#downloadRetryPromise.promise;
|
|
||||||
if (nextStep === 'retry') {
|
|
||||||
continue;
|
|
||||||
} else if (nextStep === 'cancel') {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await this.#unlinkAndDeleteAllData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are here: the user has either canceled manually, or after
|
||||||
|
// getting an error (potentially fatal).
|
||||||
|
log.warn('backups.downloadAndImport: unlinking');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.#unlinkAndDeleteAllData();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await unlink(absoluteDownloadPath);
|
await unlink(absoluteDownloadPath);
|
||||||
} catch {
|
} catch {
|
||||||
// Best-effort
|
// Best-effort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure to fail the backup import process so that background.ts
|
||||||
|
// will not wait for the syncs.
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -243,12 +232,8 @@ export class BackupsService {
|
|||||||
await window.storage.remove('backupTransitArchive');
|
await window.storage.remove('backupTransitArchive');
|
||||||
await window.storage.put('isRestoredFromBackup', hasBackup);
|
await window.storage.put('isRestoredFromBackup', hasBackup);
|
||||||
|
|
||||||
// If the primary cancels sync on their end, then we can link without sync
|
log.info('backups.downloadAndImport: done');
|
||||||
if (!hasBackup) {
|
|
||||||
window.reduxActions.installer.handleMissingBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`backups.downloadAndImport: done, had backup=${hasBackup}`);
|
|
||||||
return { wasBackupImported: hasBackup };
|
return { wasBackupImported: hasBackup };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +594,6 @@ export class BackupsService {
|
|||||||
let archive = window.storage.get('backupTransitArchive');
|
let archive = window.storage.get('backupTransitArchive');
|
||||||
if (archive == null) {
|
if (archive == null) {
|
||||||
const response = await this.api.getTransferArchive(controller.signal);
|
const response = await this.api.getTransferArchive(controller.signal);
|
||||||
|
|
||||||
if ('error' in response) {
|
if ('error' in response) {
|
||||||
switch (response.error) {
|
switch (response.error) {
|
||||||
case 'RELINK_REQUESTED':
|
case 'RELINK_REQUESTED':
|
||||||
@@ -650,6 +634,10 @@ export class BackupsService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error instanceof BackupInstallerError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
log.error(
|
log.error(
|
||||||
'backups.doDownloadAndImport: error downloading backup file',
|
'backups.doDownloadAndImport: error downloading backup file',
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
@@ -699,10 +687,10 @@ export class BackupsService {
|
|||||||
await window.storage.put('password', password);
|
await window.storage.put('password', password);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Error or manual cancel during import; this is non-retriable
|
// Error or manual cancel during import; this is non-retriable
|
||||||
if (e instanceof BackupImportCanceledError) {
|
if (e instanceof BackupInstallerError) {
|
||||||
throw e;
|
throw e;
|
||||||
} else {
|
} else {
|
||||||
throw new BackupProcessingError();
|
throw new BackupProcessingError(e);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await unlink(downloadPath);
|
await unlink(downloadPath);
|
||||||
@@ -797,6 +785,10 @@ export class BackupsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #unlinkAndDeleteAllData() {
|
async #unlinkAndDeleteAllData() {
|
||||||
|
window.reduxActions.installer.updateBackupImportProgress({
|
||||||
|
error: InstallScreenBackupError.Canceled,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.textsecure.server?.unlink();
|
await window.textsecure.server?.unlink();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ export const actions = {
|
|||||||
retryBackupImport,
|
retryBackupImport,
|
||||||
showBackupImport,
|
showBackupImport,
|
||||||
handleMissingBackup,
|
handleMissingBackup,
|
||||||
showLinkInProgress,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useInstallerActions = (): BoundActionCreatorsMapObject<
|
export const useInstallerActions = (): BoundActionCreatorsMapObject<
|
||||||
@@ -384,10 +383,6 @@ function showBackupImport(): ShowBackupImportActionType {
|
|||||||
return { type: SHOW_BACKUP_IMPORT };
|
return { type: SHOW_BACKUP_IMPORT };
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLinkInProgress(): ShowLinkInProgressActionType {
|
|
||||||
return { type: SHOW_LINK_IN_PROGRESS };
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMissingBackup(): ShowLinkInProgressActionType {
|
function handleMissingBackup(): ShowLinkInProgressActionType {
|
||||||
// If backup is missing, go to normal link-in-progress view
|
// If backup is missing, go to normal link-in-progress view
|
||||||
return { type: SHOW_LINK_IN_PROGRESS };
|
return { type: SHOW_LINK_IN_PROGRESS };
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export const SmartInstallScreen = memo(function SmartInstallScreen() {
|
|||||||
...installerState,
|
...installerState,
|
||||||
onCancel: onCancelBackupImport,
|
onCancel: onCancelBackupImport,
|
||||||
onRetry: retryBackupImport,
|
onRetry: retryBackupImport,
|
||||||
onRestartLink: startInstaller,
|
|
||||||
updates,
|
updates,
|
||||||
currentVersion: window.getVersion(),
|
currentVersion: window.getVersion(),
|
||||||
forceUpdate,
|
forceUpdate,
|
||||||
|
|||||||
@@ -362,4 +362,43 @@ describe('backups', function (this: Mocha.Suite) {
|
|||||||
await contact2Elem.click();
|
await contact2Elem.click();
|
||||||
await window.locator('.module-message >> "Message 33"').waitFor();
|
await window.locator('.module-message >> "Message 33"').waitFor();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles remote ephemeral backup cancelation', async function () {
|
||||||
|
const ephemeralBackupKey = randomBytes(32);
|
||||||
|
|
||||||
|
const { phone, server } = bootstrap;
|
||||||
|
|
||||||
|
phone.ephemeralBackupKey = ephemeralBackupKey;
|
||||||
|
|
||||||
|
app = await bootstrap.link({
|
||||||
|
ephemeralBackup: {
|
||||||
|
error: 'RELINK_REQUESTED',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
const modal = window.getByTestId(
|
||||||
|
'ConfirmationDialog.InstallScreenBackupImportStep.error'
|
||||||
|
);
|
||||||
|
|
||||||
|
await modal.waitFor();
|
||||||
|
|
||||||
|
await modal.getByRole('button', { name: 'Retry' }).click();
|
||||||
|
|
||||||
|
await window
|
||||||
|
.locator('.module-InstallScreenQrCodeNotScannedStep__qr-code--loaded')
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
debug('waiting for provision');
|
||||||
|
const provision = await server.waitForProvision();
|
||||||
|
|
||||||
|
debug('waiting for provision URL');
|
||||||
|
const provisionURL = await app.waitForProvisionURL();
|
||||||
|
|
||||||
|
debug('completing provision');
|
||||||
|
await provision.complete({
|
||||||
|
provisionURL,
|
||||||
|
primaryDevice: phone,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -119,10 +119,15 @@ export type BootstrapOptions = Readonly<{
|
|||||||
useLegacyStorageEncryption?: boolean;
|
useLegacyStorageEncryption?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type EphemeralBackupType = Readonly<{
|
export type EphemeralBackupType = Readonly<
|
||||||
cdn: 3;
|
| {
|
||||||
key: string;
|
cdn: 3;
|
||||||
}>;
|
key: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
error: 'RELINK_REQUESTED';
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
export type LinkOptionsType = Readonly<{
|
export type LinkOptionsType = Readonly<{
|
||||||
extraConfig?: Partial<RendererConfigType>;
|
extraConfig?: Partial<RendererConfigType>;
|
||||||
@@ -404,6 +409,11 @@ export class Bootstrap {
|
|||||||
|
|
||||||
if (ephemeralBackup != null) {
|
if (ephemeralBackup != null) {
|
||||||
await this.server.provideTransferArchive(this.desktop, ephemeralBackup);
|
await this.server.provideTransferArchive(this.desktop, ephemeralBackup);
|
||||||
|
|
||||||
|
// Desktop won't get linked
|
||||||
|
if ('error' in ephemeralBackup) {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('new desktop device %j', this.desktop.debugId);
|
debug('new desktop device %j', this.desktop.debugId);
|
||||||
|
|||||||
@@ -1123,8 +1123,10 @@ export default class AccountManager extends EventTarget {
|
|||||||
// until the backup is downloaded and imported.
|
// until the backup is downloaded and imported.
|
||||||
if (shouldDownloadBackup && cleanStart) {
|
if (shouldDownloadBackup && cleanStart) {
|
||||||
if (options.type === AccountType.Linked && options.ephemeralBackupKey) {
|
if (options.type === AccountType.Linked && options.ephemeralBackupKey) {
|
||||||
|
log.info('createAccount: setting ephemeral key');
|
||||||
await storage.put('backupEphemeralKey', options.ephemeralBackupKey);
|
await storage.put('backupEphemeralKey', options.ephemeralBackupKey);
|
||||||
}
|
}
|
||||||
|
log.info('createAccount: setting backup download path');
|
||||||
await storage.put('backupDownloadPath', getRelativePath(createName()));
|
await storage.put('backupDownloadPath', getRelativePath(createName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,6 @@ export class Provisioner {
|
|||||||
isLinkAndSyncEnabled() &&
|
isLinkAndSyncEnabled() &&
|
||||||
Bytes.isNotEmpty(envelope.ephemeralBackupKey),
|
Bytes.isNotEmpty(envelope.ephemeralBackupKey),
|
||||||
});
|
});
|
||||||
request.respond(200, 'OK');
|
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn(
|
||||||
'Provisioner.connect: unsupported request type',
|
'Provisioner.connect: unsupported request type',
|
||||||
|
|||||||
Reference in New Issue
Block a user