Refactor to pull loadvm into login vm

Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
This commit is contained in:
RMidhunSuresh 2021-08-20 15:19:42 +05:30
parent 80ea48e8a1
commit bdc860eb79
8 changed files with 128 additions and 87 deletions

View file

@ -133,13 +133,11 @@ export class RootViewModel extends ViewModel {
} }
_showSessionLoader(sessionId) { _showSessionLoader(sessionId) {
this._setSection(() => {
this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({
createAndStartSessionContainer: () => {
const sessionContainer = this._createSessionContainer(); const sessionContainer = this._createSessionContainer();
sessionContainer.startWithExistingSession(sessionId); sessionContainer.startWithExistingSession(sessionId);
return sessionContainer; this._setSection(() => {
}, this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({
sessionContainer,
ready: sessionContainer => this._showSession(sessionContainer) ready: sessionContainer => this._showSession(sessionContainer)
})); }));
this._sessionLoadViewModel.start(); this._sessionLoadViewModel.start();

View file

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {LoadStatus, LoginFailure} from "../matrix/SessionContainer.js"; import {LoadStatus} from "../matrix/SessionContainer.js";
import {SyncStatus} from "../matrix/Sync.js"; import {SyncStatus} from "../matrix/Sync.js";
import {ViewModel} from "./ViewModel.js"; import {ViewModel} from "./ViewModel.js";
export class SessionLoadViewModel extends ViewModel { export class SessionLoadViewModel extends ViewModel {
constructor(options) { constructor(options) {
super(options); super(options);
const {createAndStartSessionContainer, ready, homeserver, deleteSessionOnCancel} = options; const {sessionContainer, ready, homeserver, deleteSessionOnCancel} = options;
this._createAndStartSessionContainer = createAndStartSessionContainer; this._sessionContainer = sessionContainer;
this._ready = ready; this._ready = ready;
this._homeserver = homeserver; this._homeserver = homeserver;
this._deleteSessionOnCancel = deleteSessionOnCancel; this._deleteSessionOnCancel = deleteSessionOnCancel;
@ -38,7 +38,6 @@ export class SessionLoadViewModel extends ViewModel {
try { try {
this._loading = true; this._loading = true;
this.emitChange("loading"); this.emitChange("loading");
this._sessionContainer = await this._createAndStartSessionContainer();
this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => { this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => {
this.emitChange("loadLabel"); this.emitChange("loadLabel");
// wait for initial sync, but not catchup sync // wait for initial sync, but not catchup sync
@ -111,20 +110,6 @@ export class SessionLoadViewModel extends ViewModel {
if (sc) { if (sc) {
switch (sc.loadStatus.get()) { switch (sc.loadStatus.get()) {
case LoadStatus.NotLoading:
return `Preparing…`;
case LoadStatus.Login:
return `Checking your credentials…`;
case LoadStatus.LoginFailed:
switch (sc.loginFailure) {
case LoginFailure.LoginFailure:
return `Your credentials don't seem to be correct.`;
case LoginFailure.Connection:
return `Can't connect to ${this._homeserver}.`;
case LoginFailure.Unknown:
return `Something went wrong while checking your credentials.`;
}
break;
case LoadStatus.SessionSetup: case LoadStatus.SessionSetup:
return `Setting up your encryption keys…`; return `Setting up your encryption keys…`;
case LoadStatus.Loading: case LoadStatus.Loading:

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import {ViewModel} from "../ViewModel.js"; import {ViewModel} from "../ViewModel.js";
import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; import {LoginFailure} from "../../matrix/SessionContainer.js";
export class CompleteSSOLoginViewModel extends ViewModel { export class CompleteSSOLoginViewModel extends ViewModel {
constructor(options) { constructor(options) {
@ -24,17 +24,17 @@ export class CompleteSSOLoginViewModel extends ViewModel {
loginToken, loginToken,
sessionContainer, sessionContainer,
ready, ready,
attemptLogin,
showError,
} = options; } = options;
this._loginToken = loginToken; this._loginToken = loginToken;
this._ready = ready; this._ready = ready;
this._sessionContainer = sessionContainer; this._sessionContainer = sessionContainer;
this._loadViewModelSubscription = null; this._attemptLogin = attemptLogin;
this._loadViewModel = null; this._showError = showError;
this.performSSOLoginCompletion(); this.performSSOLoginCompletion();
} }
get loadViewModel() { return this._loadViewModel; }
async performSSOLoginCompletion() { async performSSOLoginCompletion() {
if (!this._loginToken) { if (!this._loginToken) {
return; return;
@ -46,25 +46,22 @@ export class CompleteSSOLoginViewModel extends ViewModel {
this.navigation.applyPath(path); this.navigation.applyPath(path);
return; return;
} }
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); const status = await this._attemptLogin(loginOptions.token(this._loginToken));
if (this._loadViewModel) { let error = "";
this._loadViewModel = this.disposeTracked(this._loadViewModel); switch (status) {
case LoginFailure.Credentials:
error = `Your login-token is invalid.`;
break;
case LoginFailure.Connection:
error = `Can't connect to ${this._homeserver}.`;
break;
case LoginFailure.Unknown:
error = `Something went wrong while checking your login-token.`;
break;
}
if (error) {
this._showError(error);
this._sessionContainer.resetStatus();
} }
this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({
createAndStartSessionContainer: async () => {
this._sessionContainer.startWithLogin(loginOptions.token(this._loginToken));
return this._sessionContainer;
},
ready: this._ready,
homeserver,
})));
this._loadViewModel.start();
this.emitChange("loadViewModel");
this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => {
if (!this._loadViewModel.loading) {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
}
this.emitChange("isBusy");
}));
} }
} }

View file

@ -18,6 +18,8 @@ import {ViewModel} from "../ViewModel.js";
import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js";
import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js"; import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js";
import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js"; import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js";
import {LoadStatus} from "../../matrix/SessionContainer.js";
import {SessionLoadViewModel} from "../SessionLoadViewModel.js";
export class LoginViewModel extends ViewModel { export class LoginViewModel extends ViewModel {
constructor(options) { constructor(options) {
@ -31,8 +33,12 @@ export class LoginViewModel extends ViewModel {
this._passwordLoginViewModel = null; this._passwordLoginViewModel = null;
this._startSSOLoginViewModel = null; this._startSSOLoginViewModel = null;
this._completeSSOLoginViewModel = null; this._completeSSOLoginViewModel = null;
this._loadViewModel = null;
this._loadViewModelSubscription = null;
this._homeserver = defaultHomeServer; this._homeserver = defaultHomeServer;
this._errorMessage = ""; this._errorMessage = "";
this._hideHomeserver = false;
this._isBusy = false;
this._createViewModels(this._homeserver); this._createViewModels(this._homeserver);
} }
@ -41,11 +47,14 @@ export class LoginViewModel extends ViewModel {
get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; }
get defaultHomeServer() { return this._homeserver; } get defaultHomeServer() { return this._homeserver; }
get errorMessage() { return this._errorMessage; } get errorMessage() { return this._errorMessage; }
get showHomeserver() { return !this._completeSSOLoginViewModel; } get showHomeserver() { return !this._hideHomeserver; }
get cancelUrl() { return this.urlCreator.urlForSegment("session"); } get cancelUrl() { return this.urlCreator.urlForSegment("session"); }
get loadViewModel() {return this._loadViewModel; }
get isBusy() { return this._isBusy; }
async _createViewModels(homeserver) { async _createViewModels(homeserver) {
if (this._loginToken) { if (this._loginToken) {
this._hideHomeserver = true;
this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken})));
this.emitChange("completeSSOLoginViewModel"); this.emitChange("completeSSOLoginViewModel");
} }
@ -61,11 +70,11 @@ export class LoginViewModel extends ViewModel {
if (this._loginOptions.sso) { this._showSSOLogin(); } if (this._loginOptions.sso) { this._showSSOLogin(); }
if (this._loginOptions.password) { this._showPasswordLogin(); } if (this._loginOptions.password) { this._showPasswordLogin(); }
if (!this._loginOptions.sso && !this._loginOptions.password) { if (!this._loginOptions.sso && !this._loginOptions.password) {
this._showError("This homeserver neither supports SSO nor Password based login flows"); this.showError("This homeserver neither supports SSO nor Password based login flows");
} }
} }
else { else {
this._showError("Could not query login methods supported by the homeserver"); this.showError("Could not query login methods supported by the homeserver");
} }
} }
} }
@ -80,14 +89,56 @@ export class LoginViewModel extends ViewModel {
this.emitChange("startSSOLoginViewModel"); this.emitChange("startSSOLoginViewModel");
} }
_showError(message) { showError(message) {
this._errorMessage = message; this._errorMessage = message;
this.emitChange("errorMessage"); this.emitChange("errorMessage");
this._errorMessage = "";
}
_toggleBusy(status) {
this._isBusy = status;
this.emitChange("isBusy");
}
async attemptLogin(loginMethod) {
this._toggleBusy(true);
this._sessionContainer.startWithLogin(loginMethod);
const loadStatus = this._sessionContainer.loadStatus;
const handle = loadStatus.waitFor(status => status !== LoadStatus.Login);
await handle.promise;
this._toggleBusy(false);
const status = loadStatus.get();
if (status === LoadStatus.LoginFailed) {
return this._sessionContainer.loginFailure;
}
this._hideHomeserver = true;
this._disposeViewModels();
this._createLoadViewModel();
return null;
}
_createLoadViewModel() {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
if (this._loadViewModel) {
this._loadViewModel = this.disposeTracked(this._loadViewModel);
}
this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions()));
this._loadViewModel.start();
this.emitChange("loadViewModel");
this._loadViewModelSubscription = this.track(
this._loadViewModel.disposableOn("change", () => {
if (!this._loadViewModel.loading) {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
}
this.emitChange("isBusy");
})
);
} }
_disposeViewModels() { _disposeViewModels() {
this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel);
this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel);
this._completeSSOLoginViewModel = this.disposeTracked(this._completeSSOLoginViewModel);
this.emitChange("disposeViewModels"); this.emitChange("disposeViewModels");
} }
@ -107,7 +158,9 @@ export class LoginViewModel extends ViewModel {
}, },
sessionContainer: this._sessionContainer, sessionContainer: this._sessionContainer,
loginOptions: this._loginOptions, loginOptions: this._loginOptions,
homeserver: this._homeserver homeserver: this._homeserver,
attemptLogin: loginMethod => this.attemptLogin(loginMethod),
showError: message => this.showError(message)
} }
} }

View file

@ -15,51 +15,47 @@ limitations under the License.
*/ */
import {ViewModel} from "../ViewModel.js"; import {ViewModel} from "../ViewModel.js";
import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; import {LoginFailure} from "../../matrix/SessionContainer.js";
export class PasswordLoginViewModel extends ViewModel { export class PasswordLoginViewModel extends ViewModel {
constructor(options) { constructor(options) {
super(options); super(options);
const {ready, loginOptions, sessionContainer, homeserver} = options; const {ready, loginOptions, sessionContainer, homeserver, attemptLogin, showError} = options;
this._ready = ready; this._ready = ready;
this._sessionContainer = sessionContainer; this._sessionContainer = sessionContainer;
this._loadViewModel = null;
this._loadViewModelSubscription = null;
this._loginOptions = loginOptions; this._loginOptions = loginOptions;
this._attemptLogin = attemptLogin;
this._showError = showError;
this._homeserver = homeserver; this._homeserver = homeserver;
this._isBusy = false;
} }
get loadViewModel() {return this._loadViewModel; } get isBusy() { return this._isBusy; }
get isBusy() { _toggleBusy(state) {
if (!this._loadViewModel) { this._isBusy = state;
return false; this.emitChange("isBusy");
} else {
return this._loadViewModel.loading;
}
} }
async login(username, password) { async login(username, password) {
const homeserver = this._homeserver; this._toggleBusy(true);
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); const status = await this._attemptLogin(this._loginOptions.password(username, password));
if (this._loadViewModel) { this._toggleBusy(false);
this._loadViewModel = this.disposeTracked(this._loadViewModel); let error = "";
switch (status) {
case LoginFailure.Credentials:
error = `Your credentials don't seem to be correct.`;
break;
case LoginFailure.Connection:
error = `Can't connect to ${this._homeserver}.`;
break;
case LoginFailure.Unknown:
error = `Something went wrong while checking your credentials.`;
break;
}
if (error) {
this._showError(error);
this._sessionContainer.resetStatus();
} }
this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({
createAndStartSessionContainer: async () => {
this._sessionContainer.startWithLogin(this._loginOptions.password(username, password));
return this._sessionContainer;
},
ready: this._ready,
homeserver,
})));
this._loadViewModel.start();
this.emitChange("loadViewModel");
this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => {
if (!this._loadViewModel.loading) {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
}
this.emitChange("isBusy");
}));
} }
} }

View file

@ -300,6 +300,10 @@ export class SessionContainer {
return this._error; return this._error;
} }
get loginFailure() {
return this._loginFailure;
}
/** only set at loadStatus InitialSync, CatchupSync or Ready */ /** only set at loadStatus InitialSync, CatchupSync or Ready */
get sync() { get sync() {
return this._sync; return this._sync;
@ -349,4 +353,10 @@ export class SessionContainer {
this._sessionId = null; this._sessionId = null;
} }
} }
resetStatus() {
this._status.set(LoadStatus.NotLoading);
this._error = null;
this._loginFailure = null;
}
} }

View file

@ -18,6 +18,7 @@ import {TemplateView} from "../general/TemplateView.js";
import {hydrogenGithubLink} from "./common.js"; import {hydrogenGithubLink} from "./common.js";
import {PasswordLoginView} from "./PasswordLoginView.js"; import {PasswordLoginView} from "./PasswordLoginView.js";
import {CompleteSSOView} from "./CompleteSSOView.js"; import {CompleteSSOView} from "./CompleteSSOView.js";
import {SessionLoadStatusView} from "./SessionLoadStatusView.js";
export class LoginView extends TemplateView { export class LoginView extends TemplateView {
render(t, vm) { render(t, vm) {
@ -34,6 +35,7 @@ export class LoginView extends TemplateView {
type: "text", type: "text",
placeholder: vm.i18n`Your matrix homeserver`, placeholder: vm.i18n`Your matrix homeserver`,
value: vm.defaultHomeServer, value: vm.defaultHomeServer,
disabled: vm => vm.isBusy,
onChange: event => vm.updateHomeServer(event.target.value), onChange: event => vm.updateHomeServer(event.target.value),
}) })
] ]
@ -41,7 +43,8 @@ export class LoginView extends TemplateView {
t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null),
t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)),
t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null),
t.if(vm => vm.errorMessage, (t, vm) => t.h5({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "LoginView_error"}, vm.i18n(vm.errorMessage))),
t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null),
// use t.mapView rather than t.if to create a new view when the view model changes too // use t.mapView rather than t.if to create a new view when the view model changes too
t.p(hydrogenGithubLink(t)) t.p(hydrogenGithubLink(t))
]); ]);

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import {TemplateView} from "../general/TemplateView.js"; import {TemplateView} from "../general/TemplateView.js";
import {SessionLoadStatusView} from "./SessionLoadStatusView.js";
export class PasswordLoginView extends TemplateView { export class PasswordLoginView extends TemplateView {
render(t, vm) { render(t, vm) {
@ -43,11 +42,11 @@ export class PasswordLoginView extends TemplateView {
}, [ }, [
t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]),
t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]),
t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null),
t.div({ className: "button-row" }, [ t.div({ className: "button-row" }, [
t.button({ t.button({
className: "button-action primary", className: "button-action primary",
type: "submit" type: "submit",
disabled
}, vm.i18n`Log In`), }, vm.i18n`Log In`),
]), ]),
]) ])