Merge pull request #560 from vector-im/bwindels/logout

Add Log out
This commit is contained in:
Bruno Windels 2021-10-26 15:10:21 +02:00 committed by GitHub
commit 0f0719eaa2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 135 deletions

View file

@ -33,44 +33,6 @@ class SessionItemViewModel extends ViewModel {
return this._error && this._error.message; 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() { get id() {
return this._sessionInfo.id; return this._sessionInfo.id;
} }
@ -96,27 +58,6 @@ class SessionItemViewModel extends ViewModel {
return this._exportDataUrl; 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() { get avatarColorNumber() {
return getIdentifierColorNumber(this._sessionInfo.userId); return getIdentifierColorNumber(this._sessionInfo.userId);
} }
@ -148,43 +89,6 @@ export class SessionPickerViewModel extends ViewModel {
return this._loadViewModel; 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() { get sessions() {
return this._sessions; return this._sessions;
} }

View file

@ -230,7 +230,7 @@ export class SessionViewModel extends ViewModel {
} }
if (settingsOpen) { if (settingsOpen) {
this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({ this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({
session: this._sessionContainer.session, sessionContainer: this._sessionContainer,
}))); })));
this._settingsViewModel.load(); this._settingsViewModel.load();
} }

View file

@ -41,18 +41,36 @@ export class SettingsViewModel extends ViewModel {
constructor(options) { constructor(options) {
super(options); super(options);
this._updateService = options.updateService; this._updateService = options.updateService;
const session = options.session; const {sessionContainer} = options;
this._session = session; this._sessionContainer = sessionContainer;
this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session}))); this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session: this._session})));
this._closeUrl = this.urlCreator.urlUntilSegment("session"); this._closeUrl = this.urlCreator.urlUntilSegment("session");
this._estimate = null; this._estimate = null;
this.sentImageSizeLimit = null; this.sentImageSizeLimit = null;
this.minSentImageSizeLimit = 400; this.minSentImageSizeLimit = 400;
this.maxSentImageSizeLimit = 4000; this.maxSentImageSizeLimit = 4000;
this.pushNotifications = new PushNotificationStatus(); 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) { setSentImageSizeLimit(size) {
if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) { if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) {
this.sentImageSizeLimit = null; this.sentImageSizeLimit = null;

View file

@ -106,6 +106,11 @@ export class Session {
return this._sessionInfo.userId; 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 // called once this._e2eeAccount is assigned
_setupEncryption() { _setupEncryption() {
// TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account // TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account

View file

@ -252,7 +252,9 @@ export class SessionContainer {
} }
}); });
await log.wrap("wait first sync", () => this._waitForFirstSync()); await log.wrap("wait first sync", () => this._waitForFirstSync());
if (this._isDisposed) {
return;
}
this._status.set(LoadStatus.Ready); this._status.set(LoadStatus.Ready);
// if the sync failed, and then the reconnector // if the sync failed, and then the reconnector
@ -261,6 +263,9 @@ export class SessionContainer {
// to prevent an extra /versions request // to prevent an extra /versions request
if (!this._sessionStartedByReconnector) { if (!this._sessionStartedByReconnector) {
const lastVersionsResponse = await hsApi.versions({timeout: 10000, log}).response(); const lastVersionsResponse = await hsApi.versions({timeout: 10000, log}).response();
if (this._isDisposed) {
return;
}
// log as ref as we don't want to await it // log as ref as we don't want to await it
await log.wrap("session start", log => this._session.start(lastVersionsResponse, log)); await log.wrap("session start", log => this._session.start(lastVersionsResponse, log));
} }
@ -322,11 +327,16 @@ export class SessionContainer {
return this._reconnector; return this._reconnector;
} }
get _isDisposed() {
return !this._reconnector;
}
dispose() { dispose() {
if (this._reconnectSubscription) { if (this._reconnectSubscription) {
this._reconnectSubscription(); this._reconnectSubscription();
this._reconnectSubscription = null; this._reconnectSubscription = null;
} }
this._reconnector = null;
if (this._requestScheduler) { if (this._requestScheduler) {
this._requestScheduler.stop(); this._requestScheduler.stop();
} }
@ -346,13 +356,17 @@ export class SessionContainer {
} }
} }
async deleteSession() { async deleteSession(log) {
if (this._sessionId) { 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 // if one fails, don't block the other from trying
// also, run in parallel // also, run in parallel
await Promise.all([ await Promise.all([
this._platform.storageFactory.delete(this._sessionId), log.wrap("storageFactory", () => this._platform.storageFactory.delete(this._sessionId)),
this._platform.sessionInfoStorage.delete(this._sessionId), log.wrap("sessionInfoStorage", () => this._platform.sessionInfoStorage.delete(this._sessionId)),
]); ]);
this._sessionId = null; this._sessionId = null;
} }

View file

@ -225,6 +225,10 @@ export class HomeServerApi {
forget(roomId, options = null) { forget(roomId, options = null) {
return this._post(`/rooms/${encodeURIComponent(roomId)}/forget`, null, null, options); 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"; import {Request as MockRequest} from "../../mocks/Request.js";

View file

@ -57,39 +57,11 @@ class SessionPickerItemView extends TemplateView {
} }
render(t, vm) { 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([ return t.li([
t.a({className: "session-info", href: vm.openUrl}, [ t.a({className: "session-info", href: vm.openUrl}, [
t.div({className: `avatar usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), t.div({className: `avatar usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials),
t.div({className: "user-id"}, vm => vm.label), t.div({className: "user-id"}, vm => vm.label),
]), ])
t.div({className: "session-actions"}, [
deleteButton,
exportButton,
downloadExport,
clearButton,
]),
errorMessage
]); ]);
} }
} }

View file

@ -40,7 +40,15 @@ export class SettingsView extends TemplateView {
t.h3("Session"), t.h3("Session"),
row(t, vm.i18n`User ID`, vm.userId), row(t, vm.i18n`User ID`, vm.userId),
row(t, vm.i18n`Session ID`, vm.deviceId, "code"), 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( settingNodes.push(
t.h3("Session Backup"), t.h3("Session Backup"),