mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-23 03:25:12 +01:00
better session backup ui
This commit is contained in:
parent
8f89c7b363
commit
6f82d81f39
@ -138,26 +138,4 @@ export class SessionStatusViewModel extends ViewModel {
|
||||
this._reconnector.tryNow();
|
||||
}
|
||||
}
|
||||
|
||||
async enterPassphrase(passphrase) {
|
||||
if (passphrase) {
|
||||
try {
|
||||
await this._session.enableSecretStorage("passphrase", passphrase);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(`Could not set up secret storage with passphrase: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async enterSecurityKey(securityKey) {
|
||||
if (securityKey) {
|
||||
try {
|
||||
await this._session.enableSecretStorage("recoverykey", securityKey);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(`Could not set up secret storage with securityKey: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ export class SettingsViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._updateService = options.updateService;
|
||||
this._session = options.session;
|
||||
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
||||
const session = options.session;
|
||||
this._session = session;
|
||||
this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session})));
|
||||
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
||||
}
|
||||
|
||||
get closeUrl() {
|
||||
@ -52,7 +54,7 @@ export class SettingsViewModel extends ViewModel {
|
||||
if (this._updateService) {
|
||||
return `${this._updateService.version} (${this._updateService.buildHash})`;
|
||||
}
|
||||
return "development version";
|
||||
return this.i18n`development version`;
|
||||
}
|
||||
|
||||
checkForUpdate() {
|
||||
@ -62,4 +64,83 @@ export class SettingsViewModel extends ViewModel {
|
||||
get showUpdateButton() {
|
||||
return !!this._updateService;
|
||||
}
|
||||
|
||||
get sessionBackupViewModel() {
|
||||
return this._sessionBackupViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SessionBackupViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._session = options.session;
|
||||
this._showKeySetup = true;
|
||||
this._error = null;
|
||||
this._isBusy = false;
|
||||
}
|
||||
|
||||
get isBusy() {
|
||||
return this._isBusy;
|
||||
}
|
||||
|
||||
get backupVersion() {
|
||||
return this._session.sessionBackup?.version;
|
||||
}
|
||||
|
||||
get status() {
|
||||
if (this._session.sessionBackup) {
|
||||
return "enabled";
|
||||
} else {
|
||||
return this._showKeySetup ? "setupKey" : "setupPhrase";
|
||||
}
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this._error?.message;
|
||||
}
|
||||
|
||||
showPhraseSetup() {
|
||||
this._showKeySetup = false;
|
||||
this.emitChange("showKeySetup");
|
||||
}
|
||||
|
||||
showKeySetup() {
|
||||
this._showKeySetup = true;
|
||||
this.emitChange("showKeySetup");
|
||||
}
|
||||
|
||||
async enterSecurityPhrase(passphrase) {
|
||||
if (passphrase) {
|
||||
try {
|
||||
this._isBusy = true;
|
||||
this.emitChange("isBusy");
|
||||
await this._session.enableSecretStorage("phrase", passphrase);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this._error = err;
|
||||
this.emitChange("error");
|
||||
} finally {
|
||||
this._isBusy = false;
|
||||
this.emitChange("isBusy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async enterSecurityKey(securityKey) {
|
||||
if (securityKey) {
|
||||
try {
|
||||
this._isBusy = true;
|
||||
this.emitChange("isBusy");
|
||||
await this._session.enableSecretStorage("key", securityKey);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this._error = err;
|
||||
this.emitChange("error");
|
||||
} finally {
|
||||
this._isBusy = false;
|
||||
this.emitChange("isBusy");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +207,10 @@ export class Session {
|
||||
this.needsSessionBackup.set(false);
|
||||
}
|
||||
|
||||
get sessionBackup() {
|
||||
return this._sessionBackup;
|
||||
}
|
||||
|
||||
// called after load
|
||||
async beforeFirstSync(isNewLogin) {
|
||||
if (this._olm) {
|
||||
|
@ -33,6 +33,10 @@ export class SessionBackup {
|
||||
return JSON.parse(sessionInfo);
|
||||
}
|
||||
|
||||
get version() {
|
||||
return this._backupInfo.version;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._decryption.free();
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ export async function keyFromCredential(type, credential, storage, cryptoDriver,
|
||||
throw new Error("Could not find a default secret storage key in account data");
|
||||
}
|
||||
let key;
|
||||
if (type === "passphrase") {
|
||||
if (type === "phrase") {
|
||||
key = await keyFromPassphrase(keyDescription, credential, cryptoDriver);
|
||||
} else if (type === "recoverykey") {
|
||||
} else if (type === "key") {
|
||||
key = keyFromRecoveryKey(olm, keyDescription, credential);
|
||||
} else {
|
||||
throw new Error(`Invalid type: ${type}`);
|
||||
|
@ -306,14 +306,9 @@ a {
|
||||
}
|
||||
|
||||
.SessionStatusView {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: #3D88FA;
|
||||
padding: 8px 4px;
|
||||
background-color: #03B381;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.middle-shown .SessionStatusView {
|
||||
@ -548,7 +543,11 @@ ul.Timeline > li.messageStatus .message-container > p {
|
||||
}
|
||||
|
||||
.SettingsBody {
|
||||
padding: 12px 16px;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.Settings h3 {
|
||||
margin: 16px 0 8px 0;
|
||||
}
|
||||
|
||||
.Settings .row .label {
|
||||
@ -583,3 +582,20 @@ ul.Timeline > li.messageStatus .message-container > p {
|
||||
.Settings .row .label {
|
||||
flex: 0 0 200px;
|
||||
}
|
||||
|
||||
.Settings .error {
|
||||
color: red;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
button.link {
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
color: #03B381;
|
||||
font-weight: 600;
|
||||
margin: -12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
@ -32,3 +32,10 @@ export class SessionStatusView extends TemplateView {
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export class SetupSecretStorageView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.p([t.a({href: vm.setupSecretStorageUrl}, vm.i18n`Set up secret storage`), vm.i18n` to decrypt older messages.`])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import {TemplateView} from "../general/TemplateView.js";
|
||||
import {StaticView} from "../general/StaticView.js";
|
||||
import {SessionStatusView} from "./SessionStatusView.js";
|
||||
import {RoomGridView} from "./RoomGridView.js";
|
||||
import {SettingsView} from "./SettingsView.js";
|
||||
import {SettingsView} from "./settings/SettingsView.js";
|
||||
|
||||
export class SessionView extends TemplateView {
|
||||
render(t, vm) {
|
||||
|
68
src/ui/web/session/settings/SessionBackupSettingsView.js
Normal file
68
src/ui/web/session/settings/SessionBackupSettingsView.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {TemplateView} from "../../general/TemplateView.js";
|
||||
|
||||
export class SessionBackupSettingsView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.mapView(vm => vm.status, status => {
|
||||
switch (status) {
|
||||
case "enabled": return new TemplateView(vm, renderEnabled)
|
||||
case "setupKey": return new TemplateView(vm, renderEnableFromKey)
|
||||
case "setupPhrase": return new TemplateView(vm, renderEnableFromPhrase)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderEnabled(t, vm) {
|
||||
return t.p(vm.i18n`Session backup is enabled, using backup version ${vm.backupVersion}.`);
|
||||
}
|
||||
|
||||
function renderEnableFromKey(t, vm) {
|
||||
return t.div([
|
||||
t.p(vm.i18n`Enter your security key below to set up session backup, which will enable you to decrypt messages received before you logged into this session. The security key is a code of 12 groups of 4 characters separated by a space that Element created for you when setting up security.`),
|
||||
t.p([vm.i18n`Alternatively, you can `, t.button({className: "link", onClick: () => vm.showPhraseSetup()}, vm.i18n`use a security phrase`), vm.i18n` if you have one.`]),
|
||||
renderError(t),
|
||||
renderEnableFieldRow(t, vm.i18n`Security key`, key => vm.enterSecurityKey(key))
|
||||
]);
|
||||
}
|
||||
|
||||
function renderEnableFromPhrase(t, vm) {
|
||||
return t.div([
|
||||
t.p(vm.i18n`Enter your security phrase below to set up session backup, which will enable you to decrypt messages received before you logged into this session. The security phrase is a freeform secret phrase you optionally chose when setting up security in Element. It is different from your password to login, unless you chose to set them to the same value.`),
|
||||
t.p([vm.i18n`You can also `, t.button({className: "link", onClick: () => vm.showKeySetup()}, vm.i18n`use your security key`), vm.i18n`.`]),
|
||||
renderError(t),
|
||||
renderEnableFieldRow(t, vm.i18n`Security phrase`, phrase => vm.enterSecurityPhrase(phrase))
|
||||
]);
|
||||
}
|
||||
|
||||
function renderEnableFieldRow(t, label, callback) {
|
||||
return t.div({className: `row`}, [
|
||||
t.div({className: "label"}, label),
|
||||
t.div({className: "content"}, t.input({type: "password", disabled: vm => vm.isBusy, placeholder: label, onChange: evt => callback(evt.target.value)})),
|
||||
]);
|
||||
}
|
||||
|
||||
function renderError(t) {
|
||||
return t.if(vm => vm.error, t.createTemplate((t, vm) => {
|
||||
return t.div([
|
||||
t.p({className: "error"}, vm => vm.i18n`Could not enable session 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.`)
|
||||
])
|
||||
}));
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {TemplateView} from "../general/TemplateView.js";
|
||||
import {TemplateView} from "../../general/TemplateView.js";
|
||||
import {SessionBackupSettingsView} from "./SessionBackupSettingsView.js"
|
||||
|
||||
export class SettingsView extends TemplateView {
|
||||
render(t, vm) {
|
||||
@ -39,9 +40,13 @@ export class SettingsView extends TemplateView {
|
||||
t.h2("Settings")
|
||||
]),
|
||||
t.div({className: "SettingsBody"}, [
|
||||
t.h3("Session"),
|
||||
row(vm.i18n`User ID`, vm.userId),
|
||||
row(vm.i18n`Session ID`, vm.deviceId, "code"),
|
||||
row(vm.i18n`Session key`, vm.fingerprintKey, "code"),
|
||||
t.h3("Session Backup"),
|
||||
t.view(new SessionBackupSettingsView(vm.sessionBackupViewModel)),
|
||||
t.h3("Application"),
|
||||
row(vm.i18n`Version`, version),
|
||||
])
|
||||
]);
|
Loading…
Reference in New Issue
Block a user