diff --git a/src/domain/session/settings/KeyBackupViewModel.js b/src/domain/session/settings/KeyBackupViewModel.js index 391aa09e..133139ed 100644 --- a/src/domain/session/settings/KeyBackupViewModel.js +++ b/src/domain/session/settings/KeyBackupViewModel.js @@ -18,7 +18,8 @@ import {ViewModel} from "../../ViewModel.js"; import {KeyType} from "../../../matrix/ssss/index"; import {createEnum} from "../../../utils/enum"; -export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending", "NewVersionAvailable"); +export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending", "NewVersionAvailable", "Error", "Cancelled"); +export const BackupWriteStatus = createEnum("Writing", "Stopped", "Done", "Pending"); export class KeyBackupViewModel extends ViewModel { constructor(options) { @@ -30,7 +31,11 @@ export class KeyBackupViewModel extends ViewModel { this._status = undefined; this._backupOperation = this._session.keyBackup.flatMap(keyBackup => keyBackup.operationInProgress); this._progress = this._backupOperation.flatMap(op => op.progress); - this.track(this._backupOperation.subscribe(() => this.emitChange("isBackingUp"))); + this.track(this._backupOperation.subscribe(() => { + // see if needsNewKey might be set + this._reevaluateStatus(); + this.emitChange("isBackingUp"); + })); this.track(this._progress.subscribe(() => this.emitChange("backupPercentage"))); this._reevaluateStatus(); this.track(this._session.keyBackup.subscribe(() => { @@ -47,7 +52,7 @@ export class KeyBackupViewModel extends ViewModel { let status; const keyBackup = this._session.keyBackup.get(); if (keyBackup) { - status = keyBackup.needsNewKey.get() ? Status.NewVersionAvailable : Status.Enabled; + status = keyBackup.needsNewKey ? Status.NewVersionAvailable : Status.Enabled; } else { status = this.showPhraseSetup() ? Status.SetupPhrase : Status.SetupKey; } /* TODO: bring back "waiting to get online" @@ -83,6 +88,27 @@ export class KeyBackupViewModel extends ViewModel { return this._session.keyBackup.get()?.version; } + get backupWriteStatus() { + const keyBackup = this._session.keyBackup.get(); + if (!keyBackup) { + return BackupWriteStatus.Pending; + } else if (keyBackup.hasStopped) { + return BackupWriteStatus.Stopped; + } + const operation = keyBackup.operationInProgress.get(); + if (operation) { + return BackupWriteStatus.Writing; + } else if (keyBackup.hasBackedUpAllKeys) { + return BackupWriteStatus.Done; + } else { + return BackupWriteStatus.Pending; + } + } + + get backupError() { + return this._session.keyBackup.get()?.error?.message; + } + get status() { return this._status; } @@ -171,7 +197,11 @@ export class KeyBackupViewModel extends ViewModel { } cancelBackup() { - this._session.keyBackup.get()?.operationInProgress.get()?.abort(); + this._backupOperation.get()?.abort(); + } + + startBackup() { + this._session.keyBackup.get()?.flush(); } } diff --git a/src/platform/web/ui/session/settings/KeyBackupSettingsView.js b/src/platform/web/ui/session/settings/KeyBackupSettingsView.js index 0d5d27e0..3f8812c9 100644 --- a/src/platform/web/ui/session/settings/KeyBackupSettingsView.js +++ b/src/platform/web/ui/session/settings/KeyBackupSettingsView.js @@ -22,22 +22,36 @@ export class KeyBackupSettingsView extends TemplateView { t.map(vm => vm.status, (status, t, vm) => { switch (status) { case "Enabled": return renderEnabled(t, vm); + case "NewVersionAvailable": return renderNewVersionAvailable(t, vm); case "SetupKey": return renderEnableFromKey(t, vm); case "SetupPhrase": return renderEnableFromPhrase(t, vm); - case "NewVersionAvailable": return t.p(vm.i18n`A new backup version has been created. Disable key backup and enable it again with the new key.`); case "Pending": return t.p(vm.i18n`Waiting to go onlineā€¦`); } }), - t.map(vm => vm.isBackingUp, (backingUp, t, vm) => { - if (backingUp) { - const progress = t.progress({ - min: 0, - max: 100, - value: vm => vm.backupPercentage, - }); - return t.div([`Backup in progress `, progress, " ", vm => vm.backupInProgressLabel]); - } else { - return t.p("All keys are backed up."); + t.map(vm => vm.backupWriteStatus, (status, t, vm) => { + switch (status) { + case "Writing": { + const progress = t.progress({ + min: 0, + max: 100, + value: vm => vm.backupPercentage, + }); + return t.div([`Backup in progress `, progress, " ", vm => vm.backupInProgressLabel]); + } + case "Stopped": { + let label; + const error = vm.backupError; + if (error) { + label = `Backup has stopped because of an error: ${vm.backupError}`; + } else { + label = `Backup has stopped`; + } + return t.p(label, " ", t.button({onClick: () => vm.startBackup()}, `Backup now`)); + } + case "Done": + return t.p(`All keys are backed up.`); + default: + return null; } }) ]); @@ -46,7 +60,7 @@ export class KeyBackupSettingsView extends TemplateView { function renderEnabled(t, vm) { const items = [ - t.p([vm.i18n`Session backup is enabled, using backup version ${vm.backupVersion}. `, t.button({onClick: () => vm.disable()}, vm.i18n`Disable`)]) + t.p([vm.i18n`Key backup is enabled, using backup version ${vm.backupVersion}. `, t.button({onClick: () => vm.disable()}, vm.i18n`Disable`)]) ]; if (vm.dehydratedDeviceId) { items.push(t.p(vm.i18n`A dehydrated device id was set up with id ${vm.dehydratedDeviceId} which you can use during your next login with your secret storage key.`)); @@ -54,6 +68,13 @@ function renderEnabled(t, vm) { return t.div(items); } +function renderNewVersionAvailable(t, vm) { + const items = [ + t.p([vm.i18n`A new backup version has been created from another device. Disable key backup and enable it again with the new key.`, t.button({onClick: () => vm.disable()}, vm.i18n`Disable`)]) + ]; + return t.div(items); +} + function renderEnableFromKey(t, vm) { const useASecurityPhrase = t.button({className: "link", onClick: () => vm.showPhraseSetup()}, vm.i18n`use a security phrase`); return t.div([ @@ -101,7 +122,7 @@ function renderEnableFieldRow(t, vm, label, callback) { function renderError(t) { return t.if(vm => vm.error, (t, vm) => { return t.div([ - t.p({className: "error"}, vm => vm.i18n`Could not enable session backup: ${vm.error}.`), + t.p({className: "error"}, vm => vm.i18n`Could not enable key backup: ${vm.error}.`), t.p(vm.i18n`Try double checking that you did not mix up your security key, security phrase and login password as explained above.`) ]) });