diff --git a/src/domain/session/SessionStatusViewModel.js b/src/domain/session/SessionStatusViewModel.js index 54fad4a1..4ff5e706 100644 --- a/src/domain/session/SessionStatusViewModel.js +++ b/src/domain/session/SessionStatusViewModel.js @@ -31,10 +31,11 @@ const SessionStatus = createEnum( export class SessionStatusViewModel extends ViewModel { constructor(options) { super(options); - const {sync, reconnector} = options; + const {sync, reconnector, session} = options; this._sync = sync; this._reconnector = reconnector; this._status = this._calculateState(reconnector.connectionStatus.get(), sync.status.get()); + this._session = session; } @@ -42,10 +43,13 @@ export class SessionStatusViewModel extends ViewModel { const update = () => this._updateStatus(); this.track(this._sync.status.subscribe(update)); this.track(this._reconnector.connectionStatus.subscribe(update)); + this.track(this._session.needsSessionBackup.subscribe(() => { + this.emitChange(); + })); } get isShown() { - return this._status !== SessionStatus.Syncing; + return this._session.needsSessionBackup.get() || this._status !== SessionStatus.Syncing; } get statusLabel() { @@ -61,6 +65,9 @@ export class SessionStatusViewModel extends ViewModel { case SessionStatus.SyncError: return this.i18n`Sync failed because of ${this._sync.error}`; } + if (this._session.needsSessionBackup.get()) { + return this.i18n`Some messages could not be decrypted. Connect to your secret storage to decrypt them.`; + } return ""; } @@ -122,9 +129,19 @@ export class SessionStatusViewModel extends ViewModel { return this._status === SessionStatus.Disconnected; } + get isSecretStorageShown() { + return this._session.needsSessionBackup.get(); + } + connectNow() { if (this.isConnectNowShown) { this._reconnector.tryNow(); } } + + enterPassphrase(passphrase) { + if (passphrase) { + this._session.enableSecretStorage("passphrase", passphrase); + } + } } diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 152b77ac..0a77d2a0 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -26,7 +26,8 @@ export class SessionViewModel extends ViewModel { this._session = sessionContainer.session; this._sessionStatusViewModel = this.track(new SessionStatusViewModel(this.childOptions({ sync: sessionContainer.sync, - reconnector: sessionContainer.reconnector + reconnector: sessionContainer.reconnector, + session: sessionContainer.session, }))); this._currentRoomTileViewModel = null; this._currentRoomViewModel = null; diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 934b8e91..9d9a172e 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -36,6 +36,7 @@ import { writeKey as ssssWriteKey, } from "./ssss/index.js"; import {SecretStorage} from "./ssss/SecretStorage.js"; +import {ObservableValue} from "../observable/ObservableValue.js"; const PICKLE_KEY = "DEFAULT_KEY"; @@ -74,6 +75,7 @@ export class Session { }); } this._createRoomEncryption = this._createRoomEncryption.bind(this); + this.needsSessionBackup = new ObservableValue(false); } // called once this._e2eeAccount is assigned @@ -140,6 +142,11 @@ export class Session { storage: this._storage, sessionBackup: this._sessionBackup, encryptionParams, + notifyMissingMegolmSession: () => { + if (!this._sessionBackup) { + this.needsSessionBackup.set(true) + } + }, }); } @@ -185,6 +192,7 @@ export class Session { } } } + this.needsSessionBackup.set(false); } // called after load diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index be6d2d6e..06485510 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -22,7 +22,7 @@ import {makeTxnId} from "../common.js"; const ENCRYPTED_TYPE = "m.room.encrypted"; export class RoomEncryption { - constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage, sessionBackup}) { + constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage, sessionBackup, notifyMissingMegolmSession}) { this._room = room; this._deviceTracker = deviceTracker; this._olmEncryption = olmEncryption; @@ -38,15 +38,18 @@ export class RoomEncryption { this._senderDeviceCache = new Map(); this._storage = storage; this._sessionBackup = sessionBackup; + this._notifyMissingMegolmSession = notifyMissingMegolmSession; } - enableSessionBackup(sessionBackup) { + async enableSessionBackup(sessionBackup) { if (this._sessionBackup) { return; } this._sessionBackup = sessionBackup; - // TODO: query session backup for all missing sessions so far - // can we query multiple? no, only for sessionId, all for room, or all + for(const key of this._eventIdsByMissingSession.keys()) { + const [senderKey, sessionId] = key.split("|"); + await this._requestMissingSessionFromBackup(senderKey, sessionId); + } } notifyTimelineClosed() { diff --git a/src/ui/web/session/SessionStatusView.js b/src/ui/web/session/SessionStatusView.js index 648ef232..b410b6d2 100644 --- a/src/ui/web/session/SessionStatusView.js +++ b/src/ui/web/session/SessionStatusView.js @@ -26,6 +26,7 @@ export class SessionStatusView extends TemplateView { spinner(t, {hidden: vm => !vm.isWaiting}), t.p(vm => vm.statusLabel), t.if(vm => vm.isConnectNowShown, t.createTemplate(t => t.button({onClick: () => vm.connectNow()}, "Retry now"))), + t.if(vm => vm.isSecretStorageShown, t.createTemplate(t => t.button({onClick: () => vm.enterPassphrase(prompt("passphrase"))}, "Enter passphrase"))), window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : "" ]); }