Convert src/Lifecycle.ts to TypeScript

pull/21833/head
J. Ryan Stinnett 2020-10-07 12:14:36 +01:00
parent 11eb9b59e6
commit 1b255e42c3
3 changed files with 81 additions and 57 deletions

View File

@ -18,8 +18,10 @@ limitations under the License.
*/ */
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixClient } from "matrix-js-sdk/src/client";
import {MatrixClientPeg} from './MatrixClientPeg'; import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
import EventIndexPeg from './indexing/EventIndexPeg'; import EventIndexPeg from './indexing/EventIndexPeg';
import createMatrixClient from './utils/createMatrixClient'; import createMatrixClient from './utils/createMatrixClient';
import Analytics from './Analytics'; import Analytics from './Analytics';
@ -47,44 +49,46 @@ import ThreepidInviteStore from "./stores/ThreepidInviteStore";
const HOMESERVER_URL_KEY = "mx_hs_url"; const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url"; const ID_SERVER_URL_KEY = "mx_is_url";
interface ILoadSessionOpts {
enableGuest?: boolean;
guestHsUrl?: string;
guestIsUrl?: string;
ignoreGuest?: boolean;
defaultDeviceDisplayName?: string;
fragmentQueryParams?: Record<string, string>;
}
/** /**
* Called at startup, to attempt to build a logged-in Matrix session. It tries * Called at startup, to attempt to build a logged-in Matrix session. It tries
* a number of things: * a number of things:
* *
*
* 1. if we have a guest access token in the fragment query params, it uses * 1. if we have a guest access token in the fragment query params, it uses
* that. * that.
*
* 2. if an access token is stored in local storage (from a previous session), * 2. if an access token is stored in local storage (from a previous session),
* it uses that. * it uses that.
*
* 3. it attempts to auto-register as a guest user. * 3. it attempts to auto-register as a guest user.
* *
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in * If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
* turn will raise on_logged_in and will_start_client events. * turn will raise on_logged_in and will_start_client events.
* *
* @param {object} opts * @param {object} [opts]
* * @param {object} [opts.fragmentQueryParams]: string->string map of the
* @param {object} opts.fragmentQueryParams: string->string map of the
* query-parameters extracted from the #-fragment of the starting URI. * query-parameters extracted from the #-fragment of the starting URI.
* * @param {boolean} [opts.enableGuest]: set to true to enable guest access
* @param {boolean} opts.enableGuest: set to true to enable guest access tokens * tokens and auto-guest registrations.
* and auto-guest registrations. * @param {string} [opts.guestHsUrl]: homeserver URL. Only used if enableGuest
* * is true; defines the HS to register against.
* @params {string} opts.guestHsUrl: homeserver URL. Only used if enableGuest is * @param {string} [opts.guestIsUrl]: homeserver URL. Only used if enableGuest
* true; defines the HS to register against. * is true; defines the IS to use.
* * @param {bool} [opts.ignoreGuest]: If the stored session is a guest account,
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is * ignore it and don't load it.
* true; defines the IS to use. * @param {string} [opts.defaultDeviceDisplayName]: Default display name to use
* * when registering as a guest.
* @params {bool} opts.ignoreGuest: If the stored session is a guest account, ignore
* it and don't load it.
*
* @returns {Promise} a promise which resolves when the above process completes. * @returns {Promise} a promise which resolves when the above process completes.
* Resolves to `true` if we ended up starting a session, or `false` if we * Resolves to `true` if we ended up starting a session, or `false` if we
* failed. * failed.
*/ */
export async function loadSession(opts) { export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean> {
try { try {
let enableGuest = opts.enableGuest || false; let enableGuest = opts.enableGuest || false;
const guestHsUrl = opts.guestHsUrl; const guestHsUrl = opts.guestHsUrl;
@ -97,7 +101,8 @@ export async function loadSession(opts) {
enableGuest = false; enableGuest = false;
} }
if (enableGuest && if (
enableGuest &&
fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_user_id &&
fragmentQueryParams.guest_access_token fragmentQueryParams.guest_access_token
) { ) {
@ -139,7 +144,7 @@ export async function loadSession(opts) {
* is associated with them. The session is not loaded. * is associated with them. The session is not loaded.
* @returns {String} The persisted session's owner, if an owner exists. Null otherwise. * @returns {String} The persisted session's owner, if an owner exists. Null otherwise.
*/ */
export function getStoredSessionOwner() { export function getStoredSessionOwner(): string {
const {hsUrl, userId, accessToken} = getLocalStorageSessionVars(); const {hsUrl, userId, accessToken} = getLocalStorageSessionVars();
return hsUrl && userId && accessToken ? userId : null; return hsUrl && userId && accessToken ? userId : null;
} }
@ -148,7 +153,7 @@ export function getStoredSessionOwner() {
* @returns {bool} True if the stored session is for a guest user or false if it is * @returns {bool} True if the stored session is for a guest user or false if it is
* for a real user. If there is no stored session, return null. * for a real user. If there is no stored session, return null.
*/ */
export function getStoredSessionIsGuest() { export function getStoredSessionIsGuest(): boolean {
const sessVars = getLocalStorageSessionVars(); const sessVars = getLocalStorageSessionVars();
return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null; return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null;
} }
@ -163,7 +168,10 @@ export function getStoredSessionIsGuest() {
* @returns {Promise} promise which resolves to true if we completed the token * @returns {Promise} promise which resolves to true if we completed the token
* login, else false * login, else false
*/ */
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { export function attemptTokenLogin(
queryParams: Record<string, string>,
defaultDeviceDisplayName?: string,
): Promise<boolean> {
if (!queryParams.loginToken) { if (!queryParams.loginToken) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@ -187,7 +195,7 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
return _clearStorage().then(() => { return _clearStorage().then(() => {
_persistCredentialsToLocalStorage(creds); _persistCredentialsToLocalStorage(creds);
// remember that we just logged in // remember that we just logged in
sessionStorage.setItem("mx_fresh_login", true); sessionStorage.setItem("mx_fresh_login", String(true));
return true; return true;
}); });
}).catch((err) => { }).catch((err) => {
@ -197,8 +205,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
}); });
} }
export function handleInvalidStoreError(e) { export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
if (e.reason === Matrix.InvalidStoreError.TOGGLED_LAZY_LOADING) { if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
const lazyLoadEnabled = e.value; const lazyLoadEnabled = e.value;
if (lazyLoadEnabled) { if (lazyLoadEnabled) {
@ -231,7 +239,11 @@ export function handleInvalidStoreError(e) {
} }
} }
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { function _registerAsGuest(
hsUrl: string,
isUrl: string,
defaultDeviceDisplayName: string,
): Promise<boolean> {
console.log(`Doing guest login on ${hsUrl}`); console.log(`Doing guest login on ${hsUrl}`);
// create a temporary MatrixClient to do the login // create a temporary MatrixClient to do the login
@ -259,12 +271,21 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
}); });
} }
export interface ILocalStorageSession {
hsUrl: string;
isUrl: string;
accessToken: string;
userId: string;
deviceId: string;
isGuest: boolean;
}
/** /**
* Retrieves information about the stored session in localstorage. The session * Retrieves information about the stored session in localstorage. The session
* may not be valid, as it is not tested for consistency here. * may not be valid, as it is not tested for consistency here.
* @returns {Object} Information about the session - see implementation for variables. * @returns {Object} Information about the session - see implementation for variables.
*/ */
export function getLocalStorageSessionVars() { export function getLocalStorageSessionVars(): ILocalStorageSession {
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY); const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY); const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
const accessToken = localStorage.getItem("mx_access_token"); const accessToken = localStorage.getItem("mx_access_token");
@ -292,8 +313,8 @@ export function getLocalStorageSessionVars() {
// The plan is to gradually move the localStorage access done here into // The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with // SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. isGuest etc.) // localStorage (e.g. isGuest etc.)
async function _restoreFromLocalStorage(opts) { async function _restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise<boolean> {
const ignoreGuest = opts.ignoreGuest; const ignoreGuest = opts?.ignoreGuest;
if (!localStorage) { if (!localStorage) {
return false; return false;
@ -314,7 +335,7 @@ async function _restoreFromLocalStorage(opts) {
console.log("No pickle key available"); console.log("No pickle key available");
} }
const freshLogin = sessionStorage.getItem("mx_fresh_login"); const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
sessionStorage.removeItem("mx_fresh_login"); sessionStorage.removeItem("mx_fresh_login");
console.log(`Restoring session for ${userId}`); console.log(`Restoring session for ${userId}`);
@ -335,7 +356,7 @@ async function _restoreFromLocalStorage(opts) {
} }
} }
async function _handleLoadSessionFailure(e) { async function _handleLoadSessionFailure(e: Error): Promise<boolean> {
console.error("Unable to load session", e); console.error("Unable to load session", e);
const SessionRestoreErrorDialog = const SessionRestoreErrorDialog =
@ -369,7 +390,7 @@ async function _handleLoadSessionFailure(e) {
* *
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started * @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/ */
export async function setLoggedIn(credentials) { export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<MatrixClient> {
credentials.freshLogin = true; credentials.freshLogin = true;
stopMatrixClient(); stopMatrixClient();
const pickleKey = credentials.userId && credentials.deviceId const pickleKey = credentials.userId && credentials.deviceId
@ -400,7 +421,7 @@ export async function setLoggedIn(credentials) {
* *
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started * @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/ */
export function hydrateSession(credentials) { export function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
const oldUserId = MatrixClientPeg.get().getUserId(); const oldUserId = MatrixClientPeg.get().getUserId();
const oldDeviceId = MatrixClientPeg.get().getDeviceId(); const oldDeviceId = MatrixClientPeg.get().getDeviceId();
@ -425,7 +446,10 @@ export function hydrateSession(credentials) {
* *
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started * @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/ */
async function _doSetLoggedIn(credentials, clearStorage) { async function _doSetLoggedIn(
credentials: IMatrixClientCreds,
clearStorage: boolean,
): Promise<MatrixClient> {
credentials.guest = Boolean(credentials.guest); credentials.guest = Boolean(credentials.guest);
const softLogout = isSoftLogout(); const softLogout = isSoftLogout();
@ -514,7 +538,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
return client; return client;
} }
function _showStorageEvictedDialog() { function _showStorageEvictedDialog(): Promise<boolean> {
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog'); const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
return new Promise(resolve => { return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, { Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
@ -527,7 +551,7 @@ function _showStorageEvictedDialog() {
// `instanceof`. Babel 7 supports this natively in their class handling. // `instanceof`. Babel 7 supports this natively in their class handling.
class AbortLoginAndRebuildStorage extends Error { } class AbortLoginAndRebuildStorage extends Error { }
function _persistCredentialsToLocalStorage(credentials) { function _persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void {
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl); localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
if (credentials.identityServerUrl) { if (credentials.identityServerUrl) {
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl); localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
@ -537,7 +561,7 @@ function _persistCredentialsToLocalStorage(credentials) {
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest)); localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
if (credentials.pickleKey) { if (credentials.pickleKey) {
localStorage.setItem("mx_has_pickle_key", true); localStorage.setItem("mx_has_pickle_key", String(true));
} else { } else {
if (localStorage.getItem("mx_has_pickle_key")) { if (localStorage.getItem("mx_has_pickle_key")) {
console.error("Expected a pickle key, but none provided. Encryption may not work."); console.error("Expected a pickle key, but none provided. Encryption may not work.");
@ -561,7 +585,7 @@ let _isLoggingOut = false;
/** /**
* Logs the current session out and transitions to the logged-out state * Logs the current session out and transitions to the logged-out state
*/ */
export function logout() { export function logout(): void {
if (!MatrixClientPeg.get()) return; if (!MatrixClientPeg.get()) return;
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
@ -590,7 +614,7 @@ export function logout() {
); );
} }
export function softLogout() { export function softLogout(): void {
if (!MatrixClientPeg.get()) return; if (!MatrixClientPeg.get()) return;
// Track that we've detected and trapped a soft logout. This helps prevent other // Track that we've detected and trapped a soft logout. This helps prevent other
@ -611,11 +635,11 @@ export function softLogout() {
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not. // DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
} }
export function isSoftLogout() { export function isSoftLogout(): boolean {
return localStorage.getItem("mx_soft_logout") === "true"; return localStorage.getItem("mx_soft_logout") === "true";
} }
export function isLoggingOut() { export function isLoggingOut(): boolean {
return _isLoggingOut; return _isLoggingOut;
} }
@ -625,7 +649,7 @@ export function isLoggingOut() {
* @param {boolean} startSyncing True (default) to actually start * @param {boolean} startSyncing True (default) to actually start
* syncing the client. * syncing the client.
*/ */
async function startMatrixClient(startSyncing=true) { async function startMatrixClient(startSyncing = true): Promise<void> {
console.log(`Lifecycle: Starting MatrixClient`); console.log(`Lifecycle: Starting MatrixClient`);
// dispatch this before starting the matrix client: it's used // dispatch this before starting the matrix client: it's used
@ -684,7 +708,7 @@ async function startMatrixClient(startSyncing=true) {
* Stops a running client and all related services, and clears persistent * Stops a running client and all related services, and clears persistent
* storage. Used after a session has been logged out. * storage. Used after a session has been logged out.
*/ */
export async function onLoggedOut() { export async function onLoggedOut(): Promise<void> {
_isLoggingOut = false; _isLoggingOut = false;
// Ensure that we dispatch a view change **before** stopping the client so // Ensure that we dispatch a view change **before** stopping the client so
// so that React components unmount first. This avoids React soft crashes // so that React components unmount first. This avoids React soft crashes
@ -698,7 +722,7 @@ export async function onLoggedOut() {
* @param {object} opts Options for how to clear storage. * @param {object} opts Options for how to clear storage.
* @returns {Promise} promise which resolves once the stores have been cleared * @returns {Promise} promise which resolves once the stores have been cleared
*/ */
async function _clearStorage(opts: {deleteEverything: boolean}) { async function _clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
Analytics.disable(); Analytics.disable();
if (window.localStorage) { if (window.localStorage) {
@ -736,7 +760,7 @@ async function _clearStorage(opts: {deleteEverything: boolean}) {
* @param {boolean} unsetClient True (default) to abandon the client * @param {boolean} unsetClient True (default) to abandon the client
* on MatrixClientPeg after stopping. * on MatrixClientPeg after stopping.
*/ */
export function stopMatrixClient(unsetClient=true) { export function stopMatrixClient(unsetClient = true): void {
Notifier.stop(); Notifier.stop();
UserActivity.sharedInstance().stop(); UserActivity.sharedInstance().stop();
TypingStore.sharedInstance().reset(); TypingStore.sharedInstance().reset();

View File

@ -38,7 +38,7 @@ export interface IMatrixClientCreds {
homeserverUrl: string; homeserverUrl: string;
identityServerUrl: string; identityServerUrl: string;
userId: string; userId: string;
deviceId: string; deviceId?: string;
accessToken: string; accessToken: string;
guest?: boolean; guest?: boolean;
pickleKey?: string; pickleKey?: string;

View File

@ -30,7 +30,7 @@ import 'what-input';
import Analytics from "../../Analytics"; import Analytics from "../../Analytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
import * as RoomListSorter from "../../RoomListSorter"; import * as RoomListSorter from "../../RoomListSorter";
@ -290,7 +290,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// When the session loads it'll be detected as soft logged out and a dispatch // When the session loads it'll be detected as soft logged out and a dispatch
// will be sent out to say that, triggering this MatrixChat to show the soft // will be sent out to say that, triggering this MatrixChat to show the soft
// logout page. // logout page.
Lifecycle.loadSession({}); Lifecycle.loadSession();
} }
this.accountPassword = null; this.accountPassword = null;
@ -1814,12 +1814,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.showScreen("forgot_password"); this.showScreen("forgot_password");
}; };
onRegisterFlowComplete = (credentials: object, password: string) => { onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => {
return this.onUserCompletedLoginFlow(credentials, password); return this.onUserCompletedLoginFlow(credentials, password);
}; };
// returns a promise which resolves to the new MatrixClient // returns a promise which resolves to the new MatrixClient
onRegistered(credentials: object) { onRegistered(credentials: IMatrixClientCreds) {
return Lifecycle.setLoggedIn(credentials); return Lifecycle.setLoggedIn(credentials);
} }
@ -1905,7 +1905,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
* Note: SSO users (and any others using token login) currently do not pass through * Note: SSO users (and any others using token login) currently do not pass through
* this, as they instead jump straight into the app after `attemptTokenLogin`. * this, as they instead jump straight into the app after `attemptTokenLogin`.
*/ */
onUserCompletedLoginFlow = async (credentials: object, password: string) => { onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => {
this.accountPassword = password; this.accountPassword = password;
// self-destruct the password after 5mins // self-destruct the password after 5mins
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);