Merge pull request #777 from ibeckermayer/ibeckermayer/ts-conversion-loginviewmodel

TS conversion for `LoginViewModel`
This commit is contained in:
Bruno Windels 2022-07-29 09:27:10 +00:00 committed by GitHub
commit d3e93196e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 68 deletions

View file

@ -19,6 +19,7 @@ module.exports = {
], ],
rules: { rules: {
"@typescript-eslint/no-floating-promises": 2, "@typescript-eslint/no-floating-promises": 2,
"@typescript-eslint/no-misused-promises": 2 "@typescript-eslint/no-misused-promises": 2,
"semi": ["error", "always"]
} }
}; };

View file

@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Options, ViewModel} from "./ViewModel"; import {Options as BaseOptions, ViewModel} from "./ViewModel";
import {Client} from "../matrix/Client.js"; import {Client} from "../matrix/Client.js";
type LogoutOptions = { sessionId: string; } & Options; type Options = { sessionId: string; } & BaseOptions;
export class LogoutViewModel extends ViewModel<LogoutOptions> { export class LogoutViewModel extends ViewModel<Options> {
private _sessionId: string; private _sessionId: string;
private _busy: boolean; private _busy: boolean;
private _showConfirm: boolean; private _showConfirm: boolean;
private _error?: Error; private _error?: Error;
constructor(options: LogoutOptions) { constructor(options: Options) {
super(options); super(options);
this._sessionId = options.sessionId; this._sessionId = options.sessionId;
this._busy = false; this._busy = false;

View file

@ -17,7 +17,7 @@ limitations under the License.
import {Client} from "../matrix/Client.js"; import {Client} from "../matrix/Client.js";
import {SessionViewModel} from "./session/SessionViewModel.js"; import {SessionViewModel} from "./session/SessionViewModel.js";
import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
import {LoginViewModel} from "./login/LoginViewModel.js"; import {LoginViewModel} from "./login/LoginViewModel";
import {LogoutViewModel} from "./LogoutViewModel"; import {LogoutViewModel} from "./LogoutViewModel";
import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
import {ViewModel} from "./ViewModel"; import {ViewModel} from "./ViewModel";

View file

@ -58,11 +58,11 @@ export class ViewModel<O extends Options = Options> extends EventEmitter<{change
return this._options[name]; return this._options[name];
} }
observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void) { observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void): void {
const segmentObservable = this.navigation.observe(type); const segmentObservable = this.navigation.observe(type);
const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => { const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => {
onChange(value, type); onChange(value, type);
}) });
this.track(unsubscribe); this.track(unsubscribe);
} }
@ -103,7 +103,7 @@ export class ViewModel<O extends Options = Options> extends EventEmitter<{change
// //
// translated string should probably always be bindings, unless we're fine with a refresh when changing the language? // translated string should probably always be bindings, unless we're fine with a refresh when changing the language?
// we probably are, if we're using routing with a url, we could just refresh. // we probably are, if we're using routing with a url, we could just refresh.
i18n(parts: TemplateStringsArray, ...expr: any[]) { i18n(parts: TemplateStringsArray, ...expr: any[]): string {
// just concat for now // just concat for now
let result = ""; let result = "";
for (let i = 0; i < parts.length; ++i) { for (let i = 0; i < parts.length; ++i) {

View file

@ -15,101 +15,143 @@ limitations under the License.
*/ */
import {Client} from "../../matrix/Client.js"; import {Client} from "../../matrix/Client.js";
import {ViewModel} from "../ViewModel"; import {Options as BaseOptions, ViewModel} from "../ViewModel";
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/Client.js"; import {LoadStatus} from "../../matrix/Client.js";
import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; import {SessionLoadViewModel} from "../SessionLoadViewModel.js";
import type {PasswordLoginMethod, SSOLoginHelper, TokenLoginMethod, ILoginMethod} from "../../matrix/login";
export class LoginViewModel extends ViewModel { type Options = {
constructor(options) { defaultHomeserver: string;
ready: ReadyFn;
loginToken?: string;
} & BaseOptions;
export class LoginViewModel extends ViewModel<Options> {
private _ready: ReadyFn;
private _loginToken?: string;
private _client: Client;
private _loginOptions?: LoginOptions;
private _passwordLoginViewModel?: PasswordLoginViewModel;
private _startSSOLoginViewModel?: StartSSOLoginViewModel;
private _completeSSOLoginViewModel?: CompleteSSOLoginViewModel;
private _loadViewModel?: SessionLoadViewModel;
private _loadViewModelSubscription?: () => void;
private _homeserver: string;
private _queriedHomeserver?: string;
private _abortHomeserverQueryTimeout?: () => void;
private _abortQueryOperation?: () => void;
private _hideHomeserver: boolean = false;
private _isBusy: boolean = false;
private _errorMessage: string = "";
constructor(options: Readonly<Options>) {
super(options); super(options);
const {ready, defaultHomeserver, loginToken} = options; const {ready, defaultHomeserver, loginToken} = options;
this._ready = ready; this._ready = ready;
this._loginToken = loginToken; this._loginToken = loginToken;
this._client = new Client(this.platform); this._client = new Client(this.platform);
this._loginOptions = null;
this._passwordLoginViewModel = null;
this._startSSOLoginViewModel = null;
this._completeSSOLoginViewModel = null;
this._loadViewModel = null;
this._loadViewModelSubscription = null;
this._homeserver = defaultHomeserver; this._homeserver = defaultHomeserver;
this._queriedHomeserver = null;
this._errorMessage = "";
this._hideHomeserver = false;
this._isBusy = false;
this._abortHomeserverQueryTimeout = null;
this._abortQueryOperation = null;
this._initViewModels(); this._initViewModels();
} }
get passwordLoginViewModel() { return this._passwordLoginViewModel; } get passwordLoginViewModel(): PasswordLoginViewModel {
get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } return this._passwordLoginViewModel;
get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } }
get homeserver() { return this._homeserver; }
get resolvedHomeserver() { return this._loginOptions?.homeserver; }
get errorMessage() { return this._errorMessage; }
get showHomeserver() { return !this._hideHomeserver; }
get loadViewModel() {return this._loadViewModel; }
get isBusy() { return this._isBusy; }
get isFetchingLoginOptions() { return !!this._abortQueryOperation; }
goBack() { get startSSOLoginViewModel(): StartSSOLoginViewModel {
return this._startSSOLoginViewModel;
}
get completeSSOLoginViewModel(): CompleteSSOLoginViewModel {
return this._completeSSOLoginViewModel;
}
get homeserver(): string {
return this._homeserver;
}
get resolvedHomeserver(): string | undefined {
return this._loginOptions?.homeserver;
}
get errorMessage(): string {
return this._errorMessage;
}
get showHomeserver(): boolean {
return !this._hideHomeserver;
}
get loadViewModel(): SessionLoadViewModel {
return this._loadViewModel;
}
get isBusy(): boolean {
return this._isBusy;
}
get isFetchingLoginOptions(): boolean {
return !!this._abortQueryOperation;
}
goBack(): void {
this.navigation.push("session"); this.navigation.push("session");
} }
async _initViewModels() { private _initViewModels(): void {
if (this._loginToken) { if (this._loginToken) {
this._hideHomeserver = true; this._hideHomeserver = true;
this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel( this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(
this.childOptions( this.childOptions(
{ {
client: this._client, client: this._client,
attemptLogin: loginMethod => this.attemptLogin(loginMethod), attemptLogin: (loginMethod: TokenLoginMethod) => this.attemptLogin(loginMethod),
loginToken: this._loginToken loginToken: this._loginToken
}))); })));
this.emitChange("completeSSOLoginViewModel"); this.emitChange("completeSSOLoginViewModel");
} }
else { else {
await this.queryHomeserver(); void this.queryHomeserver();
} }
} }
_showPasswordLogin() { private _showPasswordLogin(): void {
this._passwordLoginViewModel = this.track(new PasswordLoginViewModel( this._passwordLoginViewModel = this.track(new PasswordLoginViewModel(
this.childOptions({ this.childOptions({
loginOptions: this._loginOptions, loginOptions: this._loginOptions,
attemptLogin: loginMethod => this.attemptLogin(loginMethod) attemptLogin: (loginMethod: PasswordLoginMethod) => this.attemptLogin(loginMethod)
}))); })));
this.emitChange("passwordLoginViewModel"); this.emitChange("passwordLoginViewModel");
} }
_showSSOLogin() { private _showSSOLogin(): void {
this._startSSOLoginViewModel = this.track( this._startSSOLoginViewModel = this.track(
new StartSSOLoginViewModel(this.childOptions({loginOptions: this._loginOptions})) new StartSSOLoginViewModel(this.childOptions({loginOptions: this._loginOptions}))
); );
this.emitChange("startSSOLoginViewModel"); this.emitChange("startSSOLoginViewModel");
} }
_showError(message) { private _showError(message: string): void {
this._errorMessage = message; this._errorMessage = message;
this.emitChange("errorMessage"); this.emitChange("errorMessage");
} }
_setBusy(status) { private _setBusy(status: boolean): void {
this._isBusy = status; this._isBusy = status;
this._passwordLoginViewModel?.setBusy(status); this._passwordLoginViewModel?.setBusy(status);
this._startSSOLoginViewModel?.setBusy(status); this._startSSOLoginViewModel?.setBusy(status);
this.emitChange("isBusy"); this.emitChange("isBusy");
} }
async attemptLogin(loginMethod) { async attemptLogin(loginMethod: ILoginMethod): Promise<null> {
this._setBusy(true); this._setBusy(true);
this._client.startWithLogin(loginMethod, {inspectAccountSetup: true}); void this._client.startWithLogin(loginMethod, {inspectAccountSetup: true});
const loadStatus = this._client.loadStatus; const loadStatus = this._client.loadStatus;
const handle = loadStatus.waitFor(status => status !== LoadStatus.Login); const handle = loadStatus.waitFor((status: LoadStatus) => status !== LoadStatus.Login);
await handle.promise; await handle.promise;
this._setBusy(false); this._setBusy(false);
const status = loadStatus.get(); const status = loadStatus.get();
@ -119,11 +161,11 @@ export class LoginViewModel extends ViewModel {
this._hideHomeserver = true; this._hideHomeserver = true;
this.emitChange("hideHomeserver"); this.emitChange("hideHomeserver");
this._disposeViewModels(); this._disposeViewModels();
this._createLoadViewModel(); void this._createLoadViewModel();
return null; return null;
} }
_createLoadViewModel() { private _createLoadViewModel(): void {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
this._loadViewModel = this.disposeTracked(this._loadViewModel); this._loadViewModel = this.disposeTracked(this._loadViewModel);
this._loadViewModel = this.track( this._loadViewModel = this.track(
@ -139,7 +181,7 @@ export class LoginViewModel extends ViewModel {
}) })
) )
); );
this._loadViewModel.start(); void this._loadViewModel.start();
this.emitChange("loadViewModel"); this.emitChange("loadViewModel");
this._loadViewModelSubscription = this.track( this._loadViewModelSubscription = this.track(
this._loadViewModel.disposableOn("change", () => { this._loadViewModel.disposableOn("change", () => {
@ -151,22 +193,22 @@ export class LoginViewModel extends ViewModel {
); );
} }
_disposeViewModels() { private _disposeViewModels(): void {
this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); this._startSSOLoginViewModel = this.disposeTracked(this._startSSOLoginViewModel);
this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel);
this._completeSSOLoginViewModel = this.disposeTracked(this._completeSSOLoginViewModel); this._completeSSOLoginViewModel = this.disposeTracked(this._completeSSOLoginViewModel);
this.emitChange("disposeViewModels"); this.emitChange("disposeViewModels");
} }
async setHomeserver(newHomeserver) { async setHomeserver(newHomeserver: string): Promise<void> {
this._homeserver = newHomeserver; this._homeserver = newHomeserver;
// clear everything set by queryHomeserver // clear everything set by queryHomeserver
this._loginOptions = null; this._loginOptions = undefined;
this._queriedHomeserver = null; this._queriedHomeserver = undefined;
this._showError(""); this._showError("");
this._disposeViewModels(); this._disposeViewModels();
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation); this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
this.emitChange(); // multiple fields changing this.emitChange("loginViewModels"); // multiple fields changing
// also clear the timeout if it is still running // also clear the timeout if it is still running
this.disposeTracked(this._abortHomeserverQueryTimeout); this.disposeTracked(this._abortHomeserverQueryTimeout);
const timeout = this.clock.createTimeout(1000); const timeout = this.clock.createTimeout(1000);
@ -181,10 +223,10 @@ export class LoginViewModel extends ViewModel {
} }
} }
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout); this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
this.queryHomeserver(); void this.queryHomeserver();
} }
async queryHomeserver() { async queryHomeserver(): Promise<void> {
// don't repeat a query we've just done // don't repeat a query we've just done
if (this._homeserver === this._queriedHomeserver || this._homeserver === "") { if (this._homeserver === this._queriedHomeserver || this._homeserver === "") {
return; return;
@ -210,7 +252,7 @@ export class LoginViewModel extends ViewModel {
if (e.name === "AbortError") { if (e.name === "AbortError") {
return; //aborted, bail out return; //aborted, bail out
} else { } else {
this._loginOptions = null; this._loginOptions = undefined;
} }
} finally { } finally {
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation); this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
@ -228,12 +270,22 @@ export class LoginViewModel extends ViewModel {
} }
} }
dispose() { dispose(): void {
super.dispose(); super.dispose();
if (this._client) { if (this._client) {
// if we move away before we're done with initial sync // if we move away before we're done with initial sync
// delete the session // delete the session
this._client.deleteSession(); void this._client.deleteSession();
} }
} }
} }
type ReadyFn = (client: Client) => void;
// TODO: move to Client.js when its converted to typescript.
type LoginOptions = {
homeserver: string;
password?: (username: string, password: string) => PasswordLoginMethod;
sso?: SSOLoginHelper;
token?: (loginToken: string) => TokenLoginMethod;
};

View file

@ -100,6 +100,8 @@ export class Client {
}); });
} }
// TODO: When converted to typescript this should return the same type
// as this._loginOptions is in LoginViewModel.ts (LoginOptions).
_parseLoginOptions(options, homeserver) { _parseLoginOptions(options, homeserver) {
/* /*
Take server response and return new object which has two props password and sso which Take server response and return new object which has two props password and sso which

View file

@ -0,0 +1,7 @@
import {ILoginMethod} from "./LoginMethod";
import {PasswordLoginMethod} from "./PasswordLoginMethod";
import {SSOLoginHelper} from "./SSOLoginHelper";
import {TokenLoginMethod} from "./TokenLoginMethod";
export {PasswordLoginMethod, SSOLoginHelper, TokenLoginMethod, ILoginMethod};