Merge pull request #168 from vector-im/bwindels/better-session-backup-ui
Better session backup ui
This commit is contained in:
commit
14da892ea0
14 changed files with 287 additions and 64 deletions
|
@ -36,6 +36,8 @@ export class SessionStatusViewModel extends ViewModel {
|
||||||
this._reconnector = reconnector;
|
this._reconnector = reconnector;
|
||||||
this._status = this._calculateState(reconnector.connectionStatus.get(), sync.status.get());
|
this._status = this._calculateState(reconnector.connectionStatus.get(), sync.status.get());
|
||||||
this._session = session;
|
this._session = session;
|
||||||
|
this._setupSessionBackupUrl = this.urlCreator.urlForSegment("settings");
|
||||||
|
this._dismissSecretStorage = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -47,8 +49,12 @@ export class SessionStatusViewModel extends ViewModel {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get setupSessionBackupUrl () {
|
||||||
|
return this._setupSessionBackupUrl;
|
||||||
|
}
|
||||||
|
|
||||||
get isShown() {
|
get isShown() {
|
||||||
return this._session.needsSessionBackup.get() || this._status !== SessionStatus.Syncing;
|
return (this._session.needsSessionBackup.get() && !this._dismissSecretStorage) || this._status !== SessionStatus.Syncing;
|
||||||
}
|
}
|
||||||
|
|
||||||
get statusLabel() {
|
get statusLabel() {
|
||||||
|
@ -65,7 +71,7 @@ export class SessionStatusViewModel extends ViewModel {
|
||||||
return this.i18n`Sync failed because of ${this._sync.error}`;
|
return this.i18n`Sync failed because of ${this._sync.error}`;
|
||||||
}
|
}
|
||||||
if (this._session.needsSessionBackup.get()) {
|
if (this._session.needsSessionBackup.get()) {
|
||||||
return this.i18n`Set up secret storage to decrypt older messages.`;
|
return this.i18n`Set up session backup to decrypt older messages.`;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -130,7 +136,18 @@ export class SessionStatusViewModel extends ViewModel {
|
||||||
|
|
||||||
get isSecretStorageShown() {
|
get isSecretStorageShown() {
|
||||||
// TODO: we need a model here where we can have multiple messages queued up and their buttons don't bleed into each other.
|
// TODO: we need a model here where we can have multiple messages queued up and their buttons don't bleed into each other.
|
||||||
return this._status === SessionStatus.Syncing && this._session.needsSessionBackup.get();
|
return this._status === SessionStatus.Syncing && this._session.needsSessionBackup.get() && !this._dismissSecretStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canDismiss() {
|
||||||
|
return this.isSecretStorageShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
if (this.isSecretStorageShown) {
|
||||||
|
this._dismissSecretStorage = true;
|
||||||
|
this.emitChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectNow() {
|
connectNow() {
|
||||||
|
@ -138,26 +155,4 @@ export class SessionStatusViewModel extends ViewModel {
|
||||||
this._reconnector.tryNow();
|
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
|
||||||
import {RoomViewModel} from "./room/RoomViewModel.js";
|
import {RoomViewModel} from "./room/RoomViewModel.js";
|
||||||
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
|
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
|
||||||
import {RoomGridViewModel} from "./RoomGridViewModel.js";
|
import {RoomGridViewModel} from "./RoomGridViewModel.js";
|
||||||
import {SettingsViewModel} from "./SettingsViewModel.js";
|
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
|
||||||
import {ViewModel} from "../ViewModel.js";
|
import {ViewModel} from "../ViewModel.js";
|
||||||
|
|
||||||
export class SessionViewModel extends ViewModel {
|
export class SessionViewModel extends ViewModel {
|
||||||
|
|
91
src/domain/session/settings/SessionBackupViewModel.js
Normal file
91
src/domain/session/settings/SessionBackupViewModel.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
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 {ViewModel} from "../../ViewModel.js";
|
||||||
|
|
||||||
|
export 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("status");
|
||||||
|
}
|
||||||
|
|
||||||
|
showKeySetup() {
|
||||||
|
this._showKeySetup = true;
|
||||||
|
this.emitChange("status");
|
||||||
|
}
|
||||||
|
|
||||||
|
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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -15,13 +14,26 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ViewModel} from "../ViewModel.js";
|
import {ViewModel} from "../../ViewModel.js";
|
||||||
|
import {SessionBackupViewModel} from "./SessionBackupViewModel.js";
|
||||||
|
|
||||||
|
function formatKey(key) {
|
||||||
|
const partLength = 4;
|
||||||
|
const partCount = Math.ceil(key.length / partLength);
|
||||||
|
let formattedKey = "";
|
||||||
|
for (let i = 0; i < partCount; i += 1) {
|
||||||
|
formattedKey += (formattedKey.length ? " " : "") + key.slice(i * partLength, (i + 1) * partLength);
|
||||||
|
}
|
||||||
|
return formattedKey;
|
||||||
|
}
|
||||||
|
|
||||||
export class SettingsViewModel extends ViewModel {
|
export class SettingsViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this._updateService = options.updateService;
|
this._updateService = options.updateService;
|
||||||
this._session = options.session;
|
const session = options.session;
|
||||||
|
this._session = session;
|
||||||
|
this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session})));
|
||||||
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +42,7 @@ export class SettingsViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
get fingerprintKey() {
|
get fingerprintKey() {
|
||||||
const key = this._session.fingerprintKey;
|
return formatKey(this._session.fingerprintKey);
|
||||||
const partLength = 4;
|
|
||||||
const partCount = Math.ceil(key.length / partLength);
|
|
||||||
let formattedKey = "";
|
|
||||||
for (let i = 0; i < partCount; i += 1) {
|
|
||||||
formattedKey += (formattedKey.length ? " " : "") + key.slice(i * partLength, (i + 1) * partLength);
|
|
||||||
}
|
|
||||||
return formattedKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get deviceId() {
|
get deviceId() {
|
||||||
|
@ -52,7 +57,7 @@ export class SettingsViewModel extends ViewModel {
|
||||||
if (this._updateService) {
|
if (this._updateService) {
|
||||||
return `${this._updateService.version} (${this._updateService.buildHash})`;
|
return `${this._updateService.version} (${this._updateService.buildHash})`;
|
||||||
}
|
}
|
||||||
return "development version";
|
return this.i18n`development version`;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForUpdate() {
|
checkForUpdate() {
|
||||||
|
@ -62,4 +67,8 @@ export class SettingsViewModel extends ViewModel {
|
||||||
get showUpdateButton() {
|
get showUpdateButton() {
|
||||||
return !!this._updateService;
|
return !!this._updateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sessionBackupViewModel() {
|
||||||
|
return this._sessionBackupViewModel;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -207,6 +207,10 @@ export class Session {
|
||||||
this.needsSessionBackup.set(false);
|
this.needsSessionBackup.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sessionBackup() {
|
||||||
|
return this._sessionBackup;
|
||||||
|
}
|
||||||
|
|
||||||
// called after load
|
// called after load
|
||||||
async beforeFirstSync(isNewLogin) {
|
async beforeFirstSync(isNewLogin) {
|
||||||
if (this._olm) {
|
if (this._olm) {
|
||||||
|
|
|
@ -33,6 +33,10 @@ export class SessionBackup {
|
||||||
return JSON.parse(sessionInfo);
|
return JSON.parse(sessionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this._backupInfo.version;
|
||||||
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._decryption.free();
|
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");
|
throw new Error("Could not find a default secret storage key in account data");
|
||||||
}
|
}
|
||||||
let key;
|
let key;
|
||||||
if (type === "passphrase") {
|
if (type === "phrase") {
|
||||||
key = await keyFromPassphrase(keyDescription, credential, cryptoDriver);
|
key = await keyFromPassphrase(keyDescription, credential, cryptoDriver);
|
||||||
} else if (type === "recoverykey") {
|
} else if (type === "key") {
|
||||||
key = keyFromRecoveryKey(olm, keyDescription, credential);
|
key = keyFromRecoveryKey(olm, keyDescription, credential);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid type: ${type}`);
|
throw new Error(`Invalid type: ${type}`);
|
||||||
|
|
|
@ -24,10 +24,3 @@ limitations under the License.
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SessionStatusView button {
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
color: currentcolor;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
4
src/ui/web/css/themes/element/icons/dismiss.svg
Normal file
4
src/ui/web/css/themes/element/icons/dismiss.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1.33313 1.33313L6.66646 6.66646" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M6.66699 1.33313L1.33366 6.66646" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 307 B |
|
@ -306,18 +306,34 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.SessionStatusView {
|
.SessionStatusView {
|
||||||
padding: 5px;
|
padding: 4px;
|
||||||
position: absolute;
|
min-height: 22px;
|
||||||
top: 20px;
|
background-color: #03B381;
|
||||||
right: 20px;
|
|
||||||
background-color: #3D88FA;
|
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 10px;
|
align-items: center;
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.middle-shown .SessionStatusView {
|
|
||||||
top: 72px;
|
.SessionStatusView button.link {
|
||||||
|
color: currentcolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SessionStatusView > .end {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SessionStatusView .dismiss {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
background-image: url('icons/dismiss.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 32px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-placeholder {
|
.room-placeholder {
|
||||||
|
@ -548,7 +564,15 @@ ul.Timeline > li.messageStatus .message-container > p {
|
||||||
}
|
}
|
||||||
|
|
||||||
.SettingsBody {
|
.SettingsBody {
|
||||||
padding: 12px 16px;
|
padding: 0px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Settings h3 {
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Settings p {
|
||||||
|
max-width: 700px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Settings .row .label {
|
.Settings .row .label {
|
||||||
|
@ -583,3 +607,23 @@ ul.Timeline > li.messageStatus .message-container > p {
|
||||||
.Settings .row .label {
|
.Settings .row .label {
|
||||||
flex: 0 0 200px;
|
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;
|
||||||
|
margin: -12px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Settings a, .Settings .link {
|
||||||
|
color: #03B381;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
|
@ -25,10 +25,9 @@ export class SessionStatusView extends TemplateView {
|
||||||
}}, [
|
}}, [
|
||||||
spinner(t, {hidden: vm => !vm.isWaiting}),
|
spinner(t, {hidden: vm => !vm.isWaiting}),
|
||||||
t.p(vm => vm.statusLabel),
|
t.p(vm => vm.statusLabel),
|
||||||
t.if(vm => vm.isConnectNowShown, t.createTemplate(t => t.button({onClick: () => vm.connectNow()}, "Retry now"))),
|
t.if(vm => vm.isConnectNowShown, t.createTemplate(t => t.button({className: "link", onClick: () => vm.connectNow()}, "Retry now"))),
|
||||||
t.if(vm => vm.isSecretStorageShown, t.createTemplate(t => t.button({onClick: () => vm.enterPassphrase(prompt("Passphrase"))}, "Enter passphrase"))),
|
t.if(vm => vm.isSecretStorageShown, t.createTemplate(t => t.a({href: vm.setupSessionBackupUrl}, "Go to settings"))),
|
||||||
t.if(vm => vm.isSecretStorageShown, t.createTemplate(t => t.button({onClick: () => vm.enterSecurityKey(prompt("Security key"))}, "Enter security key"))),
|
t.if(vm => vm.canDismiss, t.createTemplate(t => t.div({className: "end"}, t.button({className: "dismiss", onClick: () => vm.dismiss()})))),
|
||||||
window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : ""
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {TemplateView} from "../general/TemplateView.js";
|
||||||
import {StaticView} from "../general/StaticView.js";
|
import {StaticView} from "../general/StaticView.js";
|
||||||
import {SessionStatusView} from "./SessionStatusView.js";
|
import {SessionStatusView} from "./SessionStatusView.js";
|
||||||
import {RoomGridView} from "./RoomGridView.js";
|
import {RoomGridView} from "./RoomGridView.js";
|
||||||
import {SettingsView} from "./SettingsView.js";
|
import {SettingsView} from "./settings/SettingsView.js";
|
||||||
|
|
||||||
export class SessionView extends TemplateView {
|
export class SessionView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
|
|
75
src/ui/web/session/settings/SessionBackupSettingsView.js
Normal file
75
src/ui/web/session/settings/SessionBackupSettingsView.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
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) {
|
||||||
|
const useASecurityPhrase = t.button({className: "link", onClick: () => vm.showPhraseSetup()}, vm.i18n`use a security phrase`);
|
||||||
|
return t.div([
|
||||||
|
t.p(vm.i18n`Enter your secret storage 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 `, useASecurityPhrase, vm.i18n` if you have one.`]),
|
||||||
|
renderError(t),
|
||||||
|
renderEnableFieldRow(t, vm, vm.i18n`Security key`, key => vm.enterSecurityKey(key))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEnableFromPhrase(t, vm) {
|
||||||
|
const useASecurityKey = t.button({className: "link", onClick: () => vm.showKeySetup()}, vm.i18n`use your security key`);
|
||||||
|
return t.div([
|
||||||
|
t.p(vm.i18n`Enter your secret storage 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 `, useASecurityKey, vm.i18n`.`]),
|
||||||
|
renderError(t),
|
||||||
|
renderEnableFieldRow(t, vm, vm.i18n`Security phrase`, phrase => vm.enterSecurityPhrase(phrase))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEnableFieldRow(t, vm, label, callback) {
|
||||||
|
const eventHandler = () => callback(input.value);
|
||||||
|
const input = t.input({type: "password", disabled: vm => vm.isBusy, placeholder: label, onChange: eventHandler});
|
||||||
|
return t.div({className: `row`}, [
|
||||||
|
t.div({className: "label"}, label),
|
||||||
|
t.div({className: "content"}, [
|
||||||
|
input,
|
||||||
|
t.button({disabled: vm => vm.isBusy, onClick: eventHandler}, vm.i18n`Set up`),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
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 {
|
export class SettingsView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
|
@ -39,9 +40,13 @@ export class SettingsView extends TemplateView {
|
||||||
t.h2("Settings")
|
t.h2("Settings")
|
||||||
]),
|
]),
|
||||||
t.div({className: "SettingsBody"}, [
|
t.div({className: "SettingsBody"}, [
|
||||||
|
t.h3("Session"),
|
||||||
row(vm.i18n`User ID`, vm.userId),
|
row(vm.i18n`User ID`, vm.userId),
|
||||||
row(vm.i18n`Session ID`, vm.deviceId, "code"),
|
row(vm.i18n`Session ID`, vm.deviceId, "code"),
|
||||||
row(vm.i18n`Session key`, vm.fingerprintKey, "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),
|
row(vm.i18n`Version`, version),
|
||||||
])
|
])
|
||||||
]);
|
]);
|
Reference in a new issue