Handle backup transfer errors during linking
This commit is contained in:
parent
079590b4e5
commit
dbe92c02f2
6 changed files with 92 additions and 32 deletions
|
@ -1660,7 +1660,7 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info('afterStart: backup downloaded, resolving');
|
log.info('afterStart: backup download attempt completed, resolving');
|
||||||
backupReady.resolve();
|
backupReady.resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('afterStart: backup download failed, rejecting');
|
log.error('afterStart: backup download failed, rejecting');
|
||||||
|
|
|
@ -15,6 +15,8 @@ import type {
|
||||||
import type { BackupCredentials } from './credentials';
|
import type { BackupCredentials } from './credentials';
|
||||||
import { BackupCredentialType } from '../../types/backups';
|
import { BackupCredentialType } from '../../types/backups';
|
||||||
import { uploadFile } from '../../util/uploadAttachment';
|
import { uploadFile } from '../../util/uploadAttachment';
|
||||||
|
import { ContinueWithoutSyncingError, RelinkRequestedError } from './errors';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
|
||||||
export type DownloadOptionsType = Readonly<{
|
export type DownloadOptionsType = Readonly<{
|
||||||
downloadOffset: number;
|
downloadOffset: number;
|
||||||
|
@ -109,10 +111,23 @@ export class BackupAPI {
|
||||||
onProgress,
|
onProgress,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
}: DownloadOptionsType): Promise<Readable> {
|
}: DownloadOptionsType): Promise<Readable> {
|
||||||
const { cdn, key } = await this.server.getTransferArchive({
|
const response = await this.server.getTransferArchive({
|
||||||
abortSignal,
|
abortSignal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ('error' in response) {
|
||||||
|
switch (response.error) {
|
||||||
|
case 'RELINK_REQUESTED':
|
||||||
|
throw new RelinkRequestedError();
|
||||||
|
case 'CONTINUE_WITHOUT_UPLOAD':
|
||||||
|
throw new ContinueWithoutSyncingError();
|
||||||
|
default:
|
||||||
|
throw missingCaseError(response.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cdn, key } = response;
|
||||||
|
|
||||||
return this.server.getEphemeralBackupStream({
|
return this.server.getEphemeralBackupStream({
|
||||||
cdn,
|
cdn,
|
||||||
key,
|
key,
|
||||||
|
|
|
@ -13,3 +13,7 @@ export class UnsupportedBackupVersion extends Error {
|
||||||
export class BackupDownloadFailedError extends Error {}
|
export class BackupDownloadFailedError extends Error {}
|
||||||
|
|
||||||
export class BackupProcessingError extends Error {}
|
export class BackupProcessingError extends Error {}
|
||||||
|
|
||||||
|
export class RelinkRequestedError extends Error {}
|
||||||
|
|
||||||
|
export class ContinueWithoutSyncingError extends Error {}
|
||||||
|
|
|
@ -52,6 +52,8 @@ import { BackupType } from './types';
|
||||||
import {
|
import {
|
||||||
BackupDownloadFailedError,
|
BackupDownloadFailedError,
|
||||||
BackupProcessingError,
|
BackupProcessingError,
|
||||||
|
ContinueWithoutSyncingError,
|
||||||
|
RelinkRequestedError,
|
||||||
UnsupportedBackupVersion,
|
UnsupportedBackupVersion,
|
||||||
} from './errors';
|
} from './errors';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
|
@ -149,7 +151,15 @@ export class BackupsService {
|
||||||
this.downloadRetryPromise = explodePromise<RetryBackupImportValue>();
|
this.downloadRetryPromise = explodePromise<RetryBackupImportValue>();
|
||||||
|
|
||||||
let installerError: InstallScreenBackupError;
|
let installerError: InstallScreenBackupError;
|
||||||
if (error instanceof UnsupportedBackupVersion) {
|
if (error instanceof RelinkRequestedError) {
|
||||||
|
installerError = InstallScreenBackupError.Fatal;
|
||||||
|
log.error(
|
||||||
|
'backups.downloadAndImport: primary requested relink; unlinking & deleting data',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.unlinkAndDeleteAllData();
|
||||||
|
} else if (error instanceof UnsupportedBackupVersion) {
|
||||||
installerError = InstallScreenBackupError.UnsupportedVersion;
|
installerError = InstallScreenBackupError.UnsupportedVersion;
|
||||||
log.error(
|
log.error(
|
||||||
'backups.downloadAndImport: unsupported version',
|
'backups.downloadAndImport: unsupported version',
|
||||||
|
@ -167,30 +177,8 @@ export class BackupsService {
|
||||||
'backups.downloadAndImport: fatal error during processing; unlinking & deleting data',
|
'backups.downloadAndImport: fatal error during processing; unlinking & deleting data',
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
try {
|
await this.unlinkAndDeleteAllData();
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await window.textsecure.server?.unlink();
|
|
||||||
} catch (e) {
|
|
||||||
log.warn(
|
|
||||||
'Error while unlinking; this may be expected for the unlink operation',
|
|
||||||
Errors.toLogFormat(e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.info('backups.downloadAndImport: deleting all data');
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await window.textsecure.storage.protocol.removeAllData();
|
|
||||||
log.info(
|
|
||||||
'backups.downloadAndImport: all data deleted successfully'
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log.error(
|
|
||||||
'backups.downloadAndImport: unable to remove all data',
|
|
||||||
Errors.toLogFormat(e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
'backups.downloadAndImport: unknown error, prompting user to retry'
|
'backups.downloadAndImport: unknown error, prompting user to retry'
|
||||||
|
@ -222,6 +210,10 @@ export class BackupsService {
|
||||||
await window.storage.remove('backupEphemeralKey');
|
await window.storage.remove('backupEphemeralKey');
|
||||||
await window.storage.put('isRestoredFromBackup', hasBackup);
|
await window.storage.put('isRestoredFromBackup', hasBackup);
|
||||||
|
|
||||||
|
if (!hasBackup) {
|
||||||
|
window.reduxActions.installer.handleMissingBackup();
|
||||||
|
}
|
||||||
|
|
||||||
log.info(`backups.downloadAndImport: done, had backup=${hasBackup}`);
|
log.info(`backups.downloadAndImport: done, had backup=${hasBackup}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +555,19 @@ export class BackupsService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Primary decided to abort syncing process; continue on with no backup
|
||||||
|
if (error instanceof ContinueWithoutSyncingError) {
|
||||||
|
log.error(
|
||||||
|
'backups.doDownloadAndImport: primary requested to continue without syncing'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary wants to try link & sync again
|
||||||
|
if (error instanceof RelinkRequestedError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
log.error(
|
log.error(
|
||||||
'backups.doDownloadAndImport: error downloading backup file',
|
'backups.doDownloadAndImport: error downloading backup file',
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
|
@ -702,6 +707,28 @@ export class BackupsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async unlinkAndDeleteAllData() {
|
||||||
|
try {
|
||||||
|
await window.textsecure.server?.unlink();
|
||||||
|
} catch (e) {
|
||||||
|
log.warn(
|
||||||
|
'Error while unlinking; this may be expected for the unlink operation',
|
||||||
|
Errors.toLogFormat(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.info('backups.unlinkAndDeleteAllData: deleting all data');
|
||||||
|
await window.textsecure.storage.protocol.removeAllData();
|
||||||
|
log.info('backups.unlinkAndDeleteAllData: all data deleted successfully');
|
||||||
|
} catch (e) {
|
||||||
|
log.error(
|
||||||
|
'backups.unlinkAndDeleteAllData: unable to remove all data',
|
||||||
|
Errors.toLogFormat(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public isImportRunning(): boolean {
|
public isImportRunning(): boolean {
|
||||||
return this.isRunning === 'import';
|
return this.isRunning === 'import';
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@ export const actions = {
|
||||||
updateBackupImportProgress,
|
updateBackupImportProgress,
|
||||||
retryBackupImport,
|
retryBackupImport,
|
||||||
showBackupImport,
|
showBackupImport,
|
||||||
|
handleMissingBackup,
|
||||||
showLinkInProgress,
|
showLinkInProgress,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -450,6 +451,11 @@ function showLinkInProgress(): ShowLinkInProgressActionType {
|
||||||
return { type: SHOW_LINK_IN_PROGRESS };
|
return { type: SHOW_LINK_IN_PROGRESS };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleMissingBackup(): ShowLinkInProgressActionType {
|
||||||
|
// If backup is missing, go to normal link-in-progress view
|
||||||
|
return { type: SHOW_LINK_IN_PROGRESS };
|
||||||
|
}
|
||||||
|
|
||||||
function updateBackupImportProgress(
|
function updateBackupImportProgress(
|
||||||
payload: UpdateBackupImportProgressActionType['payload']
|
payload: UpdateBackupImportProgressActionType['payload']
|
||||||
): UpdateBackupImportProgressActionType {
|
): UpdateBackupImportProgressActionType {
|
||||||
|
|
|
@ -1289,10 +1289,18 @@ const StickerPackUploadFormSchema = z.object({
|
||||||
stickers: z.array(StickerPackUploadAttributesSchema),
|
stickers: z.array(StickerPackUploadAttributesSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const TransferArchiveSchema = z.object({
|
const TransferArchiveSchema = z.union([
|
||||||
cdn: z.number(),
|
z.object({
|
||||||
key: z.string(),
|
cdn: z.number(),
|
||||||
});
|
key: z.string(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
error: z.union([
|
||||||
|
z.literal('RELINK_REQUESTED'),
|
||||||
|
z.literal('CONTINUE_WITHOUT_UPLOAD'),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
export type TransferArchiveType = z.infer<typeof TransferArchiveSchema>;
|
export type TransferArchiveType = z.infer<typeof TransferArchiveSchema>;
|
||||||
|
|
||||||
|
@ -2381,7 +2389,7 @@ export function initialize({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return TransferArchiveSchema.parse(data);
|
return parseUnknown(TransferArchiveSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
|
|
Loading…
Reference in a new issue