forked from mystiq/hydrogen-web
commit
0f0719eaa2
8 changed files with 60 additions and 135 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
Loading…
Reference in a new issue