/* * Wire * Copyright (C) 2022 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * */ import {useEffect, useState} from 'react'; import {Button, ButtonVariant} from '@wireapp/react-ui-kit'; import * as Icon from 'Components/Icon'; import {LoadingBar} from 'Components/LoadingBar/LoadingBar'; import {PrimaryModal} from 'Components/Modals/PrimaryModal'; import {User} from 'src/script/entity/User'; import {ContentState} from 'src/script/page/useAppState'; import {checkBackupEncryption} from 'Util/BackupUtil'; import {t} from 'Util/LocalizerUtil'; import {getLogger} from 'Util/Logger'; import {loadFileBuffer} from 'Util/util'; import {BackupFileUpload} from './BackupFileUpload'; import {BackupRepository} from '../../backup/BackupRepository'; import { CancelError, DifferentAccountError, IncompatibleBackupError, IncompatibleBackupFormatError, InvalidPassword, } from '../../backup/Error'; import {Config} from '../../Config'; import {MotionDuration} from '../../motion/MotionDuration'; export enum HistoryImportState { DONE = 'HistoryImportState.STATE.DONE', IMPORTING = 'HistoryImportState.STATE.IMPORTING', PREPARING = 'HistoryImportState.STATE.PREPARING', } interface HistoryImportProps { readonly backupRepository: BackupRepository; file: File; switchContent: (contentState: ContentState) => void; user: User; } const HistoryImport = ({user, backupRepository, file, switchContent}: HistoryImportProps) => { const logger = getLogger('HistoryImportViewModel'); const [historyImportState, setHistoryImportState] = useState(HistoryImportState.PREPARING); const [error, setError] = useState(null); const [errorHeadline, setErrorHeadline] = useState(''); const [errorSecondary, setErrorSecondary] = useState(''); const [numberOfRecords, setNumberOfRecords] = useState(0); const [numberOfProcessedRecords, setNumberOfProcessedRecords] = useState(0); const loadingProgress = Math.floor((numberOfProcessedRecords / numberOfRecords) * 100); const isPreparing = !error && historyImportState === HistoryImportState.PREPARING; const isImporting = !error && historyImportState === HistoryImportState.IMPORTING; const isDone = !error && historyImportState === HistoryImportState.DONE; const replacements = { processed: numberOfProcessedRecords.toString(), progress: loadingProgress.toString(), total: numberOfRecords.toString(), }; const historyImportMessages: Partial> = { [HistoryImportState.PREPARING]: t('backupImportProgressHeadline'), [HistoryImportState.IMPORTING]: t('backupImportProgressSecondary', replacements), }; const loadingMessage = historyImportMessages?.[historyImportState] || ''; const onCancel = () => backupRepository.cancelAction(); const dismissImport = () => { switchContent(ContentState.PREFERENCES_ACCOUNT); }; const onInit = (numberOfRecords: number) => { setHistoryImportState(HistoryImportState.IMPORTING); setNumberOfRecords(numberOfRecords); setNumberOfProcessedRecords(0); }; const onProgress = (numberProcessed: number) => setNumberOfProcessedRecords(prevState => prevState + numberProcessed); const onSuccess = (): void => { setError(null); setHistoryImportState(HistoryImportState.DONE); window.setTimeout(dismissImport, MotionDuration.X_LONG * 2); }; const onError = (error: Error) => { if (error instanceof CancelError) { logger.log('History import was cancelled'); dismissImport(); return; } setError(error); logger.error(`Failed to import history: ${error.message}`, error); if (error instanceof DifferentAccountError) { setErrorHeadline(t('backupImportAccountErrorHeadline')); setErrorSecondary(t('backupImportAccountErrorSecondary')); } else if (error instanceof IncompatibleBackupError) { setErrorHeadline(t('backupImportVersionErrorHeadline')); setErrorSecondary(t('backupImportVersionErrorSecondary', Config.getConfig().BRAND_NAME)); } else if (error instanceof IncompatibleBackupFormatError) { setErrorHeadline(t('backupImportFormatErrorHeadline')); setErrorSecondary(t('backupImportFormatErrorSecondary')); } else if (error instanceof InvalidPassword) { setErrorHeadline(t('backupImportPasswordErrorHeadline')); setErrorSecondary(t('backupImportPasswordErrorSecondary')); } else { setErrorHeadline(t('backupImportGenericErrorHeadline')); setErrorSecondary(t('backupImportGenericErrorSecondary')); } }; const getBackUpPassword = (): Promise => { return new Promise(resolve => { PrimaryModal.show(PrimaryModal.type.PASSWORD_ADVANCED_SECURITY, { primaryAction: { action: async (password: string) => { resolve(password); }, text: t('backupDecryptionModalAction'), }, secondaryAction: [ { action: () => { resolve(''); dismissImport(); }, text: t('backupEncryptionModalCloseBtn'), }, ], passwordOptional: false, text: { closeBtnLabel: t('backupEncryptionModalCloseBtn'), input: t('backupDecryptionModalPlaceholder'), message: t('backupDecryptionModalMessage'), title: t('backupDecryptionModalTitle'), }, }); }); }; const importHistory = async (file: File) => { const isEncrypted = await checkBackupEncryption(file); if (isEncrypted) { const password = await getBackUpPassword(); if (password) { await processHistoryImport(file, password); } } else { await processHistoryImport(file); } }; const processHistoryImport = async (file: File, password?: string) => { setHistoryImportState(HistoryImportState.PREPARING); setError(null); const data = await loadFileBuffer(file); try { await backupRepository.importHistory(user, data, onInit, onProgress, password); onSuccess(); } catch (error) { onError(error as Error); } }; useEffect(() => { importHistory(file); }, []); const handleFileChange = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { setError(null); await importHistory(file); } }; return (

{t('accessibility.headings.historyImport')}

{isPreparing && } {isImporting && ( <> )} {isDone && (

{t('backupImportSuccessHeadline')}

)} {error && (

{errorHeadline}

{errorSecondary}

)}
); }; export {HistoryImport};