diff --git a/src/domain/SessionPickerViewModel.js b/src/domain/SessionPickerViewModel.js index fc669df1..e4bbc7ec 100644 --- a/src/domain/SessionPickerViewModel.js +++ b/src/domain/SessionPickerViewModel.js @@ -33,44 +33,6 @@ class SessionItemViewModel extends ViewModel { return this._error && this._error.message; } - async delete() { - this._isDeleting = true; - this.emitChange("isDeleting"); - try { - await this._pickerVM.delete(this.id); - } catch(err) { - this._error = err; - console.error(err); - this.emitChange("error"); - } finally { - this._isDeleting = false; - this.emitChange("isDeleting"); - } - } - - async clear() { - this._isClearing = true; - this.emitChange(); - try { - await this._pickerVM.clear(this.id); - } catch(err) { - this._error = err; - console.error(err); - this.emitChange("error"); - } finally { - this._isClearing = false; - this.emitChange("isClearing"); - } - } - - get isDeleting() { - return this._isDeleting; - } - - get isClearing() { - return this._isClearing; - } - get id() { return this._sessionInfo.id; } @@ -96,27 +58,6 @@ class SessionItemViewModel extends ViewModel { return this._exportDataUrl; } - async export() { - try { - const data = await this._pickerVM._exportData(this._sessionInfo.id); - const json = JSON.stringify(data, undefined, 2); - const blob = new Blob([json], {type: "application/json"}); - this._exportDataUrl = URL.createObjectURL(blob); - this.emitChange("exportDataUrl"); - } catch (err) { - alert(err.message); - console.error(err); - } - } - - clearExport() { - if (this._exportDataUrl) { - URL.revokeObjectURL(this._exportDataUrl); - this._exportDataUrl = null; - this.emitChange("exportDataUrl"); - } - } - get avatarColorNumber() { return getIdentifierColorNumber(this._sessionInfo.userId); } @@ -148,43 +89,6 @@ export class SessionPickerViewModel extends ViewModel { return this._loadViewModel; } - async _exportData(id) { - const sessionInfo = await this.platform.sessionInfoStorage.get(id); - const stores = await this.logger.run("export", log => { - return this.platform.storageFactory.export(id, log); - }); - const data = {sessionInfo, stores}; - return data; - } - - async import(json) { - try { - const data = JSON.parse(json); - const {sessionInfo} = data; - sessionInfo.comment = `Imported on ${new Date().toLocaleString()} from id ${sessionInfo.id}.`; - sessionInfo.id = this._createSessionContainer().createNewSessionId(); - await this.logger.run("import", log => { - return this.platform.storageFactory.import(sessionInfo.id, data.stores, log); - }); - await this.platform.sessionInfoStorage.add(sessionInfo); - this._sessions.set(new SessionItemViewModel(sessionInfo, this)); - } catch (err) { - alert(err.message); - console.error(err); - } - } - - async delete(id) { - const idx = this._sessions.array.findIndex(s => s.id === id); - await this.platform.sessionInfoStorage.delete(id); - await this.platform.storageFactory.delete(id); - this._sessions.remove(idx); - } - - async clear(id) { - await this.platform.storageFactory.delete(id); - } - get sessions() { return this._sessions; } diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 83b7f1af..6aee0d8d 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -230,7 +230,7 @@ export class SessionViewModel extends ViewModel { } if (settingsOpen) { this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({ - session: this._sessionContainer.session, + sessionContainer: this._sessionContainer, }))); this._settingsViewModel.load(); } diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index ddb9ac47..9afd2888 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -41,18 +41,36 @@ export class SettingsViewModel extends ViewModel { constructor(options) { super(options); this._updateService = options.updateService; - const session = options.session; - this._session = session; - this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session}))); + const {sessionContainer} = options; + this._sessionContainer = sessionContainer; + this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session: this._session}))); this._closeUrl = this.urlCreator.urlUntilSegment("session"); this._estimate = null; - this.sentImageSizeLimit = null; this.minSentImageSizeLimit = 400; this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); + this._isLoggingOut = false; } + get _session() { + return this._sessionContainer.session; + } + + logout() { + return this.logger.run("logout", async log => { + this._isLoggingOut = true; + this.emitChange("isLoggingOut"); + try { + await this._session.logout(log); + } catch (err) {} + await this._sessionContainer.deleteSession(log); + this.navigation.push("session", true); + }); + } + + get isLoggingOut() { return this._isLoggingOut; } + setSentImageSizeLimit(size) { if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) { this.sentImageSizeLimit = null; diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 7047171e..235ff3b1 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -106,6 +106,11 @@ export class Session { return this._sessionInfo.userId; } + async logout(log = undefined) { + const response = await this._hsApi.logout({log}).response(); + console.log("logout", response); + } + // called once this._e2eeAccount is assigned _setupEncryption() { // TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 899b07ed..ef76971a 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -252,7 +252,9 @@ export class SessionContainer { } }); await log.wrap("wait first sync", () => this._waitForFirstSync()); - + if (this._isDisposed) { + return; + } this._status.set(LoadStatus.Ready); // if the sync failed, and then the reconnector @@ -261,6 +263,9 @@ export class SessionContainer { // to prevent an extra /versions request if (!this._sessionStartedByReconnector) { const lastVersionsResponse = await hsApi.versions({timeout: 10000, log}).response(); + if (this._isDisposed) { + return; + } // log as ref as we don't want to await it await log.wrap("session start", log => this._session.start(lastVersionsResponse, log)); } @@ -322,11 +327,16 @@ export class SessionContainer { return this._reconnector; } + get _isDisposed() { + return !this._reconnector; + } + dispose() { if (this._reconnectSubscription) { this._reconnectSubscription(); this._reconnectSubscription = null; } + this._reconnector = null; if (this._requestScheduler) { this._requestScheduler.stop(); } @@ -346,13 +356,17 @@ export class SessionContainer { } } - async deleteSession() { + async deleteSession(log) { if (this._sessionId) { + // need to dispose first, so the storage is closed, + // and also first sync finishing won't call Session.start anymore, + // which assumes that the storage works. + this.dispose(); // if one fails, don't block the other from trying // also, run in parallel await Promise.all([ - this._platform.storageFactory.delete(this._sessionId), - this._platform.sessionInfoStorage.delete(this._sessionId), + log.wrap("storageFactory", () => this._platform.storageFactory.delete(this._sessionId)), + log.wrap("sessionInfoStorage", () => this._platform.sessionInfoStorage.delete(this._sessionId)), ]); this._sessionId = null; } diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index 4b53b28b..c9beb00d 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -225,6 +225,10 @@ export class HomeServerApi { forget(roomId, options = null) { return this._post(`/rooms/${encodeURIComponent(roomId)}/forget`, null, null, options); } + + logout(options = null) { + return this._post(`/logout`, null, null, options); + } } import {Request as MockRequest} from "../../mocks/Request.js"; diff --git a/src/platform/web/ui/login/SessionPickerView.js b/src/platform/web/ui/login/SessionPickerView.js index 775aa19b..4bde0189 100644 --- a/src/platform/web/ui/login/SessionPickerView.js +++ b/src/platform/web/ui/login/SessionPickerView.js @@ -57,39 +57,11 @@ class SessionPickerItemView extends TemplateView { } render(t, vm) { - const deleteButton = t.button({ - className: "destructive", - disabled: vm => vm.isDeleting, - onClick: this._onDeleteClick.bind(this), - }, "Sign Out"); - const clearButton = t.button({ - disabled: vm => vm.isClearing, - onClick: this._onClearClick.bind(this), - }, "Clear"); - const exportButton = t.button({ - disabled: vm => vm.isClearing, - onClick: () => vm.export(), - }, "Export"); - const downloadExport = t.if(vm => vm.exportDataUrl, (t, vm) => { - return t.a({ - href: vm.exportDataUrl, - download: `brawl-session-${vm.id}.json`, - onClick: () => setTimeout(() => vm.clearExport(), 100), - }, "Download"); - }); - const errorMessage = t.if(vm => vm.error, t => t.p({className: "error"}, vm => vm.error)); return t.li([ t.a({className: "session-info", href: vm.openUrl}, [ t.div({className: `avatar usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), t.div({className: "user-id"}, vm => vm.label), - ]), - t.div({className: "session-actions"}, [ - deleteButton, - exportButton, - downloadExport, - clearButton, - ]), - errorMessage + ]) ]); } } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 8fbc6812..482abeb7 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -40,7 +40,15 @@ export class SettingsView extends TemplateView { t.h3("Session"), row(t, vm.i18n`User ID`, vm.userId), row(t, vm.i18n`Session ID`, vm.deviceId, "code"), - row(t, vm.i18n`Session key`, vm.fingerprintKey, "code") + row(t, vm.i18n`Session key`, vm.fingerprintKey, "code"), + row(t, "", t.button({ + onClick: () => { + if (confirm(vm.i18n`Are you sure you want to log out?`)) { + vm.logout(); + } + }, + disabled: vm => vm.isLoggingOut + }, vm.i18n`Log out`)) ); settingNodes.push( t.h3("Session Backup"),