From 21c1179f8d2726a909c5540f8e25a92fa0070dc3 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Thu, 20 May 2021 17:54:42 +0100
Subject: [PATCH 01/13] Update extensions for more files with types

This migrates the another bucket of files using some amount of Flow typing to
mark them as TypeScript instead. The remaining type errors are fixed in
subsequent commits.
---
 ...eAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} | 0
 .../views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx}       | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename src/components/views/auth/{InteractiveAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} (100%)
 rename src/components/views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx} (100%)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
similarity index 100%
rename from src/components/views/auth/InteractiveAuthEntryComponents.js
rename to src/components/views/auth/InteractiveAuthEntryComponents.tsx
diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.tsx
similarity index 100%
rename from src/components/views/dialogs/DevtoolsDialog.js
rename to src/components/views/dialogs/DevtoolsDialog.tsx

From 6574ca98fa098e3690391cfc0152ccaca2ea4cfd Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Wed, 12 May 2021 14:06:10 +0100
Subject: [PATCH 02/13] Fix basic lint errors

---
 .../auth/InteractiveAuthEntryComponents.tsx   |  4 +-
 .../views/dialogs/DevtoolsDialog.tsx          | 57 +++++++++++++++----
 2 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index e34349c474..5a492b14ee 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -786,7 +786,9 @@ export class FallbackAuthEntry extends React.Component {
         }
         return (
             <div>
-                <a href="" ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
+                <a href="" ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{
+                    _t("Start authentication")
+                }</a>
                 {errorSection}
             </div>
         );
diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 8a035263cc..1d544af315 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -169,8 +169,16 @@ export class SendCustomEvent extends GenericEditor {
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
                 { showTglFlip && <div style={{float: "right"}}>
-                    <input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
-                    <label className="mx_DevTools_tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
+                    <input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
+                        type="checkbox"
+                        checked={this.state.isStateEvent}
+                        onChange={this._onChange}
+                    />
+                    <label className="mx_DevTools_tgl-btn"
+                        data-tg-off="Event"
+                        data-tg-on="State Event"
+                        htmlFor="isStateEvent"
+                    />
                 </div> }
             </div>
         </div>;
@@ -253,8 +261,17 @@ class SendAccountData extends GenericEditor {
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
                 { !this.state.message && <div style={{float: "right"}}>
-                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} disabled={this.props.forceMode} />
-                    <label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
+                        type="checkbox"
+                        checked={this.state.isRoomAccountData}
+                        disabled={this.props.forceMode}
+                        onChange={this._onChange}
+                    />
+                    <label className="mx_DevTools_tgl-btn"
+                        data-tg-off="Account Data"
+                        data-tg-on="Room Data"
+                        htmlFor="isRoomAccountData"
+                    />
                 </div> }
             </div>
         </div>;
@@ -581,8 +598,16 @@ class AccountDataExplorer extends React.PureComponent {
             <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <div style={{float: "right"}}>
-                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} />
-                    <label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
+                    <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
+                        type="checkbox"
+                        checked={this.state.isRoomAccountData}
+                        onChange={this._onChange}
+                    />
+                    <label className="mx_DevTools_tgl-btn"
+                        data-tg-off="Account Data"
+                        data-tg-on="Room Data"
+                        htmlFor="isRoomAccountData"
+                    />
                 </div> }
             </div>
         </div>;
@@ -1062,27 +1087,37 @@ class SettingsExplorer extends React.Component {
 
                         <div>
                             {_t("Value:")}&nbsp;
-                            <code>{this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting))}</code>
+                            <code>{this.renderSettingValue(
+                                SettingsStore.getValue(this.state.viewSetting),
+                            )}</code>
                         </div>
 
                         <div>
                             {_t("Value in this room:")}&nbsp;
-                            <code>{this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting, room.roomId))}</code>
+                            <code>{this.renderSettingValue(
+                                SettingsStore.getValue(this.state.viewSetting, room.roomId),
+                            )}</code>
                         </div>
 
                         <div>
                             {_t("Values at explicit levels:")}
-                            <pre><code>{this.renderExplicitSettingValues(this.state.viewSetting, null)}</code></pre>
+                            <pre><code>{this.renderExplicitSettingValues(
+                                this.state.viewSetting, null,
+                            )}</code></pre>
                         </div>
 
                         <div>
                             {_t("Values at explicit levels in this room:")}
-                            <pre><code>{this.renderExplicitSettingValues(this.state.viewSetting, room.roomId)}</code></pre>
+                            <pre><code>{this.renderExplicitSettingValues(
+                                this.state.viewSetting, room.roomId,
+                            )}</code></pre>
                         </div>
 
                     </div>
                     <div className="mx_Dialog_buttons">
-                        <button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{_t("Edit Values")}</button>
+                        <button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{
+                            _t("Edit Values")
+                        }</button>
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
                 </div>

From df09bdf823e3c3cc018827c93f95ebf73e58a288 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Wed, 12 May 2021 19:28:22 +0100
Subject: [PATCH 03/13] Add types to InteractiveAuthEntryComponents

---
 src/Terms.ts                                  |  14 +-
 .../auth/InteractiveAuthEntryComponents.tsx   | 371 ++++++++++--------
 src/languageHandler.tsx                       |   8 +-
 3 files changed, 213 insertions(+), 180 deletions(-)

diff --git a/src/Terms.ts b/src/Terms.ts
index 1bdff36cbc..1b1c152fdd 100644
--- a/src/Terms.ts
+++ b/src/Terms.ts
@@ -36,14 +36,18 @@ export class Service {
     }
 }
 
-interface Policy {
+export interface LocalisedPolicy {
+    name: string;
+    url: string;
+}
+
+export interface Policy {
     // @ts-ignore: No great way to express indexed types together with other keys
     version: string;
-    [lang: string]: {
-        url: string;
-    };
+    [lang: string]: LocalisedPolicy;
 }
-type Policies = {
+
+export type Policies = {
     [policy: string]: Policy,
 };
 
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index 5a492b14ee..066c064cc1 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -1,7 +1,5 @@
 /*
-Copyright 2016 OpenMarket Ltd
-Copyright 2017 Vector Creations Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -16,9 +14,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, {createRef} from 'react';
-import PropTypes from 'prop-types';
-import classnames from 'classnames';
+import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react';
+import classNames from 'classnames';
+import { MatrixClient } from "matrix-js-sdk/src/client";
 
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
@@ -27,6 +25,7 @@ import AccessibleButton from "../elements/AccessibleButton";
 import Spinner from "../elements/Spinner";
 import CountlyAnalytics from "../../../CountlyAnalytics";
 import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { LocalisedPolicy, Policies } from '../../../Terms';
 
 /* This file contains a collection of components which are used by the
  * InteractiveAuth to prompt the user to enter the information needed
@@ -74,21 +73,49 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
  *    focus: set the input focus appropriately in the form.
  */
 
+enum AuthType {
+    Password = "m.login.password",
+    Recaptcha = "m.login.recaptcha",
+    Terms = "m.login.terms",
+    Email = "m.login.email.identity",
+    Msisdn = "m.login.msisdn",
+    Sso = "m.login.sso",
+    SsoUnstable = "org.matrix.login.sso",
+}
+
+/* eslint-disable camelcase */
+interface IAuthDict {
+    type?: AuthType;
+    // TODO: Remove `user` once servers support proper UIA
+    // See https://github.com/vector-im/element-web/issues/10312
+    user?: string;
+    identifier?: object;
+    password?: string;
+    response?: string;
+    // TODO: Remove `threepid_creds` once servers support proper UIA
+    // See https://github.com/vector-im/element-web/issues/10312
+    // See https://github.com/matrix-org/matrix-doc/issues/2220
+    threepid_creds?: object;
+    threepidCreds?: object;
+}
+/* eslint-enable camelcase */
+
 export const DEFAULT_PHASE = 0;
 
-@replaceableComponent("views.auth.PasswordAuthEntry")
-export class PasswordAuthEntry extends React.Component {
-    static LOGIN_TYPE = "m.login.password";
+interface IAuthEntryProps {
+    matrixClient: MatrixClient;
+    loginType: string;
+    authSessionId: string;
+    submitAuthDict: (auth: IAuthDict) => void;
+    errorText?: string;
+    // Is the auth logic currently waiting for something to happen?
+    busy?: boolean;
+    onPhaseChange: (phase: number) => void;
+}
 
-    static propTypes = {
-        matrixClient: PropTypes.object.isRequired,
-        submitAuthDict: PropTypes.func.isRequired,
-        errorText: PropTypes.string,
-        // is the auth logic currently waiting for something to
-        // happen?
-        busy: PropTypes.bool,
-        onPhaseChange: PropTypes.func.isRequired,
-    };
+@replaceableComponent("views.auth.PasswordAuthEntry")
+export class PasswordAuthEntry extends React.Component<IAuthEntryProps> {
+    static LOGIN_TYPE = AuthType.Password;
 
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
@@ -98,12 +125,12 @@ export class PasswordAuthEntry extends React.Component {
         password: "",
     };
 
-    _onSubmit = e => {
+    private onSubmit = (e: FormEvent) => {
         e.preventDefault();
         if (this.props.busy) return;
 
         this.props.submitAuthDict({
-            type: PasswordAuthEntry.LOGIN_TYPE,
+            type: AuthType.Password,
             // TODO: Remove `user` once servers support proper UIA
             // See https://github.com/vector-im/element-web/issues/10312
             user: this.props.matrixClient.credentials.userId,
@@ -115,7 +142,7 @@ export class PasswordAuthEntry extends React.Component {
         });
     };
 
-    _onPasswordFieldChange = ev => {
+    private onPasswordFieldChange = (ev: ChangeEvent<HTMLInputElement>) => {
         // enable the submit button iff the password is non-empty
         this.setState({
             password: ev.target.value,
@@ -123,7 +150,7 @@ export class PasswordAuthEntry extends React.Component {
     };
 
     render() {
-        const passwordBoxClass = classnames({
+        const passwordBoxClass = classNames({
             "error": this.props.errorText,
         });
 
@@ -155,7 +182,7 @@ export class PasswordAuthEntry extends React.Component {
         return (
             <div>
                 <p>{ _t("Confirm your identity by entering your account password below.") }</p>
-                <form onSubmit={this._onSubmit} className="mx_InteractiveAuthEntryComponents_passwordSection">
+                <form onSubmit={this.onSubmit} className="mx_InteractiveAuthEntryComponents_passwordSection">
                     <Field
                         className={passwordBoxClass}
                         type="password"
@@ -163,7 +190,7 @@ export class PasswordAuthEntry extends React.Component {
                         label={_t('Password')}
                         autoFocus={true}
                         value={this.state.password}
-                        onChange={this._onPasswordFieldChange}
+                        onChange={this.onPasswordFieldChange}
                     />
                     <div className="mx_button_row">
                         { submitButtonOrSpinner }
@@ -175,26 +202,26 @@ export class PasswordAuthEntry extends React.Component {
     }
 }
 
-@replaceableComponent("views.auth.RecaptchaAuthEntry")
-export class RecaptchaAuthEntry extends React.Component {
-    static LOGIN_TYPE = "m.login.recaptcha";
-
-    static propTypes = {
-        submitAuthDict: PropTypes.func.isRequired,
-        stageParams: PropTypes.object.isRequired,
-        errorText: PropTypes.string,
-        busy: PropTypes.bool,
-        onPhaseChange: PropTypes.func.isRequired,
+/* eslint-disable camelcase */
+interface IRecaptchaAuthEntryProps extends IAuthEntryProps {
+    stageParams?: {
+        public_key?: string;
     };
+}
+/* eslint-enable camelcase */
+
+@replaceableComponent("views.auth.RecaptchaAuthEntry")
+export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps> {
+    static LOGIN_TYPE = AuthType.Recaptcha;
 
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
     }
 
-    _onCaptchaResponse = response => {
+    private onCaptchaResponse = (response: string) => {
         CountlyAnalytics.instance.track("onboarding_grecaptcha_submit");
         this.props.submitAuthDict({
-            type: RecaptchaAuthEntry.LOGIN_TYPE,
+            type: AuthType.Recaptcha,
             response: response,
         });
     };
@@ -230,7 +257,7 @@ export class RecaptchaAuthEntry extends React.Component {
         return (
             <div>
                 <CaptchaForm sitePublicKey={sitePublicKey}
-                    onCaptchaResponse={this._onCaptchaResponse}
+                    onCaptchaResponse={this.onCaptchaResponse}
                 />
                 { errorSection }
             </div>
@@ -238,18 +265,28 @@ export class RecaptchaAuthEntry extends React.Component {
     }
 }
 
-@replaceableComponent("views.auth.TermsAuthEntry")
-export class TermsAuthEntry extends React.Component {
-    static LOGIN_TYPE = "m.login.terms";
-
-    static propTypes = {
-        submitAuthDict: PropTypes.func.isRequired,
-        stageParams: PropTypes.object.isRequired,
-        errorText: PropTypes.string,
-        busy: PropTypes.bool,
-        showContinue: PropTypes.bool,
-        onPhaseChange: PropTypes.func.isRequired,
+interface ITermsAuthEntryProps extends IAuthEntryProps {
+    stageParams?: {
+        policies?: Policies;
     };
+    showContinue: boolean;
+}
+
+interface LocalisedPolicyWithId extends LocalisedPolicy {
+    id: string;
+}
+
+interface ITermsAuthEntryState {
+    policies: LocalisedPolicyWithId[];
+    toggledPolicies: {
+        [policy: string]: boolean;
+    };
+    errorText?: string;
+}
+
+@replaceableComponent("views.auth.TermsAuthEntry")
+export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITermsAuthEntryState> {
+    static LOGIN_TYPE = AuthType.Terms;
 
     constructor(props) {
         super(props);
@@ -294,8 +331,11 @@ export class TermsAuthEntry extends React.Component {
 
             initToggles[policyId] = false;
 
-            langPolicy.id = policyId;
-            pickedPolicies.push(langPolicy);
+            pickedPolicies.push({
+                id: policyId,
+                name: langPolicy.name,
+                url: langPolicy.url,
+            });
         }
 
         this.state = {
@@ -312,10 +352,10 @@ export class TermsAuthEntry extends React.Component {
     }
 
     tryContinue = () => {
-        this._trySubmit();
+        this.trySubmit();
     };
 
-    _togglePolicy(policyId) {
+    private togglePolicy(policyId: string) {
         const newToggles = {};
         for (const policy of this.state.policies) {
             let checked = this.state.toggledPolicies[policy.id];
@@ -326,7 +366,7 @@ export class TermsAuthEntry extends React.Component {
         this.setState({"toggledPolicies": newToggles});
     }
 
-    _trySubmit = () => {
+    private trySubmit = () => {
         let allChecked = true;
         for (const policy of this.state.policies) {
             const checked = this.state.toggledPolicies[policy.id];
@@ -334,7 +374,7 @@ export class TermsAuthEntry extends React.Component {
         }
 
         if (allChecked) {
-            this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
+            this.props.submitAuthDict({type: AuthType.Terms});
             CountlyAnalytics.instance.track("onboarding_terms_complete");
         } else {
             this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
@@ -356,7 +396,7 @@ export class TermsAuthEntry extends React.Component {
             checkboxes.push(
                 // XXX: replace with StyledCheckbox
                 <label key={"policy_checkbox_" + policy.id} className="mx_InteractiveAuthEntryComponents_termsPolicy">
-                    <input type="checkbox" onChange={() => this._togglePolicy(policy.id)} checked={checked} />
+                    <input type="checkbox" onChange={() => this.togglePolicy(policy.id)} checked={checked} />
                     <a href={policy.url} target="_blank" rel="noreferrer noopener">{ policy.name }</a>
                 </label>,
             );
@@ -375,7 +415,7 @@ export class TermsAuthEntry extends React.Component {
         if (this.props.showContinue !== false) {
             // XXX: button classes
             submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
-                onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
+                onClick={this.trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
         }
 
         return (
@@ -389,21 +429,18 @@ export class TermsAuthEntry extends React.Component {
     }
 }
 
-@replaceableComponent("views.auth.EmailIdentityAuthEntry")
-export class EmailIdentityAuthEntry extends React.Component {
-    static LOGIN_TYPE = "m.login.email.identity";
-
-    static propTypes = {
-        matrixClient: PropTypes.object.isRequired,
-        submitAuthDict: PropTypes.func.isRequired,
-        authSessionId: PropTypes.string.isRequired,
-        clientSecret: PropTypes.string.isRequired,
-        inputs: PropTypes.object.isRequired,
-        stageState: PropTypes.object.isRequired,
-        fail: PropTypes.func.isRequired,
-        setEmailSid: PropTypes.func.isRequired,
-        onPhaseChange: PropTypes.func.isRequired,
+interface IEmailIdentityAuthEntryProps extends IAuthEntryProps {
+    inputs?: {
+        emailAddress?: string;
     };
+    stageState?: {
+        emailSid: string;
+    };
+}
+
+@replaceableComponent("views.auth.EmailIdentityAuthEntry")
+export class EmailIdentityAuthEntry extends React.Component<IEmailIdentityAuthEntryProps> {
+    static LOGIN_TYPE = AuthType.Email;
 
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
@@ -427,7 +464,7 @@ export class EmailIdentityAuthEntry extends React.Component {
             return (
                 <div className="mx_InteractiveAuthEntryComponents_emailWrapper">
                     <p>{ _t("A confirmation email has been sent to %(emailAddress)s",
-                        { emailAddress: (sub) => <b>{ this.props.inputs.emailAddress }</b> },
+                        { emailAddress: <b>{ this.props.inputs.emailAddress }</b> },
                     ) }
                     </p>
                     <p>{ _t("Open the link in the email to continue registration.") }</p>
@@ -437,37 +474,34 @@ export class EmailIdentityAuthEntry extends React.Component {
     }
 }
 
-@replaceableComponent("views.auth.MsisdnAuthEntry")
-export class MsisdnAuthEntry extends React.Component {
-    static LOGIN_TYPE = "m.login.msisdn";
-
-    static propTypes = {
-        inputs: PropTypes.shape({
-            phoneCountry: PropTypes.string,
-            phoneNumber: PropTypes.string,
-        }),
-        fail: PropTypes.func,
-        clientSecret: PropTypes.func,
-        submitAuthDict: PropTypes.func.isRequired,
-        matrixClient: PropTypes.object,
-        onPhaseChange: PropTypes.func.isRequired,
+interface IMsisdnAuthEntryProps extends IAuthEntryProps {
+    inputs: {
+        phoneCountry: string;
+        phoneNumber: string;
     };
+    clientSecret: string;
+    fail: (error: Error) => void;
+}
+
+@replaceableComponent("views.auth.MsisdnAuthEntry")
+export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps> {
+    static LOGIN_TYPE = AuthType.Msisdn;
+
+    private submitUrl: string;
+    private sid: string;
+    private msisdn: string;
 
     state = {
         token: '',
         requestingToken: false,
+        errorText: '',
     };
 
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
 
-        this._submitUrl = null;
-        this._sid = null;
-        this._msisdn = null;
-        this._tokenBox = null;
-
         this.setState({requestingToken: true});
-        this._requestMsisdnToken().catch((e) => {
+        this.requestMsisdnToken().catch((e) => {
             this.props.fail(e);
         }).finally(() => {
             this.setState({requestingToken: false});
@@ -477,26 +511,26 @@ export class MsisdnAuthEntry extends React.Component {
     /*
      * Requests a verification token by SMS.
      */
-    _requestMsisdnToken() {
+    private requestMsisdnToken(): Promise<void> {
         return this.props.matrixClient.requestRegisterMsisdnToken(
             this.props.inputs.phoneCountry,
             this.props.inputs.phoneNumber,
             this.props.clientSecret,
             1, // TODO: Multiple send attempts?
         ).then((result) => {
-            this._submitUrl = result.submit_url;
-            this._sid = result.sid;
-            this._msisdn = result.msisdn;
+            this.submitUrl = result.submit_url;
+            this.sid = result.sid;
+            this.msisdn = result.msisdn;
         });
     }
 
-    _onTokenChange = e => {
+    private onTokenChange = (e: ChangeEvent<HTMLInputElement>) => {
         this.setState({
             token: e.target.value,
         });
     };
 
-    _onFormSubmit = async e => {
+    private onFormSubmit = async (e: FormEvent) => {
         e.preventDefault();
         if (this.state.token == '') return;
 
@@ -506,20 +540,20 @@ export class MsisdnAuthEntry extends React.Component {
 
         try {
             let result;
-            if (this._submitUrl) {
+            if (this.submitUrl) {
                 result = await this.props.matrixClient.submitMsisdnTokenOtherUrl(
-                    this._submitUrl, this._sid, this.props.clientSecret, this.state.token,
+                    this.submitUrl, this.sid, this.props.clientSecret, this.state.token,
                 );
             } else {
                 throw new Error("The registration with MSISDN flow is misconfigured");
             }
             if (result.success) {
                 const creds = {
-                    sid: this._sid,
+                    sid: this.sid,
                     client_secret: this.props.clientSecret,
                 };
                 this.props.submitAuthDict({
-                    type: MsisdnAuthEntry.LOGIN_TYPE,
+                    type: AuthType.Msisdn,
                     // TODO: Remove `threepid_creds` once servers support proper UIA
                     // See https://github.com/vector-im/element-web/issues/10312
                     // See https://github.com/matrix-org/matrix-doc/issues/2220
@@ -543,7 +577,7 @@ export class MsisdnAuthEntry extends React.Component {
             return <Loader />;
         } else {
             const enableSubmit = Boolean(this.state.token);
-            const submitClasses = classnames({
+            const submitClasses = classNames({
                 mx_InteractiveAuthEntryComponents_msisdnSubmit: true,
                 mx_GeneralButton: true,
             });
@@ -558,16 +592,16 @@ export class MsisdnAuthEntry extends React.Component {
             return (
                 <div>
                     <p>{ _t("A text message has been sent to %(msisdn)s",
-                        { msisdn: <i>{ this._msisdn }</i> },
+                        { msisdn: <i>{ this.msisdn }</i> },
                     ) }
                     </p>
                     <p>{ _t("Please enter the code it contains:") }</p>
                     <div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
-                        <form onSubmit={this._onFormSubmit}>
+                        <form onSubmit={this.onFormSubmit}>
                             <input type="text"
                                 className="mx_InteractiveAuthEntryComponents_msisdnEntry"
                                 value={this.state.token}
-                                onChange={this._onTokenChange}
+                                onChange={this.onTokenChange}
                                 aria-label={ _t("Code")}
                             />
                             <br />
@@ -584,40 +618,40 @@ export class MsisdnAuthEntry extends React.Component {
     }
 }
 
-@replaceableComponent("views.auth.SSOAuthEntry")
-export class SSOAuthEntry extends React.Component {
-    static propTypes = {
-        matrixClient: PropTypes.object.isRequired,
-        authSessionId: PropTypes.string.isRequired,
-        loginType: PropTypes.string.isRequired,
-        submitAuthDict: PropTypes.func.isRequired,
-        errorText: PropTypes.string,
-        onPhaseChange: PropTypes.func.isRequired,
-        continueText: PropTypes.string,
-        continueKind: PropTypes.string,
-        onCancel: PropTypes.func,
-    };
+interface ISSOAuthEntryProps extends IAuthEntryProps {
+    continueText?: string;
+    continueKind?: string;
+    onCancel?: () => void;
+}
 
-    static LOGIN_TYPE = "m.login.sso";
-    static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso";
+interface ISSOAuthEntryState {
+    phase: number;
+    attemptFailed: boolean;
+}
+
+@replaceableComponent("views.auth.SSOAuthEntry")
+export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEntryState> {
+    static LOGIN_TYPE = AuthType.Sso;
+    static UNSTABLE_LOGIN_TYPE = AuthType.SsoUnstable;
 
     static PHASE_PREAUTH = 1; // button to start SSO
     static PHASE_POSTAUTH = 2; // button to confirm SSO completed
 
-    _ssoUrl: string;
+    private ssoUrl: string;
+    private popupWindow: Window;
 
     constructor(props) {
         super(props);
 
         // We actually send the user through fallback auth so we don't have to
         // deal with a redirect back to us, losing application context.
-        this._ssoUrl = props.matrixClient.getFallbackAuthUrl(
+        this.ssoUrl = props.matrixClient.getFallbackAuthUrl(
             this.props.loginType,
             this.props.authSessionId,
         );
 
-        this._popupWindow = null;
-        window.addEventListener("message", this._onReceiveMessage);
+        this.popupWindow = null;
+        window.addEventListener("message", this.onReceiveMessage);
 
         this.state = {
             phase: SSOAuthEntry.PHASE_PREAUTH,
@@ -625,15 +659,15 @@ export class SSOAuthEntry extends React.Component {
         };
     }
 
-    componentDidMount(): void {
+    componentDidMount() {
         this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH);
     }
 
     componentWillUnmount() {
-        window.removeEventListener("message", this._onReceiveMessage);
-        if (this._popupWindow) {
-            this._popupWindow.close();
-            this._popupWindow = null;
+        window.removeEventListener("message", this.onReceiveMessage);
+        if (this.popupWindow) {
+            this.popupWindow.close();
+            this.popupWindow = null;
         }
     }
 
@@ -643,11 +677,11 @@ export class SSOAuthEntry extends React.Component {
         });
     };
 
-    _onReceiveMessage = event => {
+    private onReceiveMessage = (event: MessageEvent) => {
         if (event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl()) {
-            if (this._popupWindow) {
-                this._popupWindow.close();
-                this._popupWindow = null;
+            if (this.popupWindow) {
+                this.popupWindow.close();
+                this.popupWindow = null;
             }
         }
     };
@@ -657,7 +691,7 @@ export class SSOAuthEntry extends React.Component {
         // certainly will need to open the thing in a new tab to avoid losing application
         // context.
 
-        this._popupWindow = window.open(this._ssoUrl, "_blank");
+        this.popupWindow = window.open(this.ssoUrl, "_blank");
         this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH});
         this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
     };
@@ -716,46 +750,37 @@ export class SSOAuthEntry extends React.Component {
 }
 
 @replaceableComponent("views.auth.FallbackAuthEntry")
-export class FallbackAuthEntry extends React.Component {
-    static propTypes = {
-        matrixClient: PropTypes.object.isRequired,
-        authSessionId: PropTypes.string.isRequired,
-        loginType: PropTypes.string.isRequired,
-        submitAuthDict: PropTypes.func.isRequired,
-        errorText: PropTypes.string,
-        onPhaseChange: PropTypes.func.isRequired,
-    };
+export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
+    private popupWindow: Window;
+    private fallbackButton = createRef<HTMLAnchorElement>();
 
     constructor(props) {
         super(props);
 
         // we have to make the user click a button, as browsers will block
         // the popup if we open it immediately.
-        this._popupWindow = null;
-        window.addEventListener("message", this._onReceiveMessage);
-
-        this._fallbackButton = createRef();
+        this.popupWindow = null;
+        window.addEventListener("message", this.onReceiveMessage);
     }
 
-
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
     }
 
     componentWillUnmount() {
-        window.removeEventListener("message", this._onReceiveMessage);
-        if (this._popupWindow) {
-            this._popupWindow.close();
+        window.removeEventListener("message", this.onReceiveMessage);
+        if (this.popupWindow) {
+            this.popupWindow.close();
         }
     }
 
     focus = () => {
-        if (this._fallbackButton.current) {
-            this._fallbackButton.current.focus();
+        if (this.fallbackButton.current) {
+            this.fallbackButton.current.focus();
         }
     };
 
-    _onShowFallbackClick = e => {
+    private onShowFallbackClick = (e: MouseEvent) => {
         e.preventDefault();
         e.stopPropagation();
 
@@ -763,10 +788,10 @@ export class FallbackAuthEntry extends React.Component {
             this.props.loginType,
             this.props.authSessionId,
         );
-        this._popupWindow = window.open(url, "_blank");
+        this.popupWindow = window.open(url, "_blank");
     };
 
-    _onReceiveMessage = event => {
+    private onReceiveMessage = (event: MessageEvent) => {
         if (
             event.data === "authDone" &&
             event.origin === this.props.matrixClient.getHomeserverUrl()
@@ -786,7 +811,7 @@ export class FallbackAuthEntry extends React.Component {
         }
         return (
             <div>
-                <a href="" ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{
+                <a href="" ref={this.fallbackButton} onClick={this.onShowFallbackClick}>{
                     _t("Start authentication")
                 }</a>
                 {errorSection}
@@ -795,20 +820,22 @@ export class FallbackAuthEntry extends React.Component {
     }
 }
 
-const AuthEntryComponents = [
-    PasswordAuthEntry,
-    RecaptchaAuthEntry,
-    EmailIdentityAuthEntry,
-    MsisdnAuthEntry,
-    TermsAuthEntry,
-    SSOAuthEntry,
-];
-
-export default function getEntryComponentForLoginType(loginType) {
-    for (const c of AuthEntryComponents) {
-        if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) {
-            return c;
-        }
+export default function getEntryComponentForLoginType(loginType: AuthType): typeof React.Component {
+    switch (loginType) {
+        case AuthType.Password:
+            return PasswordAuthEntry;
+        case AuthType.Recaptcha:
+            return RecaptchaAuthEntry;
+        case AuthType.Email:
+            return EmailIdentityAuthEntry;
+        case AuthType.Msisdn:
+            return MsisdnAuthEntry;
+        case AuthType.Terms:
+            return TermsAuthEntry;
+        case AuthType.Sso:
+        case AuthType.SsoUnstable:
+            return SSOAuthEntry;
+        default:
+            return FallbackAuthEntry;
     }
-    return FallbackAuthEntry;
 }
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index 26c89afec6..16950dc008 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -105,12 +105,14 @@ function safeCounterpartTranslate(text: string, options?: object) {
     return translated;
 }
 
+type SubstitutionValue = number | string | React.ReactNode | ((sub: string) => React.ReactNode);
+
 export interface IVariables {
     count?: number;
-    [key: string]: number | string;
+    [key: string]: SubstitutionValue;
 }
 
-type Tags = Record<string, (sub: string) => React.ReactNode>;
+type Tags = Record<string, SubstitutionValue>;
 
 export type TranslatedString = string | React.ReactNode;
 
@@ -247,7 +249,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
                 let replaced;
                 // If substitution is a function, call it
                 if (mapping[regexpString] instanceof Function) {
-                    replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups);
+                    replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups);
                 } else {
                     replaced = mapping[regexpString];
                 }

From d9e490926b5bd4f0601f826a6918c954d41791d8 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Tue, 18 May 2021 15:20:08 +0100
Subject: [PATCH 04/13] Add types to DevtoolsDialog

---
 .../views/dialogs/DevtoolsDialog.tsx          | 392 ++++++++++--------
 1 file changed, 221 insertions(+), 171 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 1d544af315..81d3a77327 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2018-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,8 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, {useState, useEffect} from 'react';
-import PropTypes from 'prop-types';
+import React, {useState, useEffect, ChangeEvent, MouseEvent} from 'react';
 import * as sdk from '../../../index';
 import SyntaxHighlight from '../elements/SyntaxHighlight';
 import { _t } from '../../../languageHandler';
@@ -30,8 +30,9 @@ import {
     PHASE_DONE,
     PHASE_STARTED,
     PHASE_CANCELLED,
+    VerificationRequest,
 } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
-import WidgetStore from "../../../stores/WidgetStore";
+import WidgetStore, { IApp } from "../../../stores/WidgetStore";
 import {UPDATE_EVENT} from "../../../stores/AsyncStore";
 import {SETTINGS} from "../../../settings/Settings";
 import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore";
@@ -40,17 +41,22 @@ import ErrorDialog from "./ErrorDialog";
 import {replaceableComponent} from "../../../utils/replaceableComponent";
 import {Room} from "matrix-js-sdk/src/models/room";
 import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+import { SettingLevel } from '../../../settings/SettingLevel';
 
-class GenericEditor extends React.PureComponent {
-    // static propTypes = {onBack: PropTypes.func.isRequired};
+interface IGenericEditorProps {
+    onBack: () => void;
+}
 
-    constructor(props) {
-        super(props);
-        this._onChange = this._onChange.bind(this);
-        this.onBack = this.onBack.bind(this);
-    }
+interface IGenericEditorState {
+    message?: string;
+    [inputId: string]: boolean | string;
+}
 
-    onBack() {
+abstract class GenericEditor<
+    P extends IGenericEditorProps = IGenericEditorProps,
+    S extends IGenericEditorState = IGenericEditorState,
+> extends React.PureComponent<P, S> {
+    protected onBack = () => {
         if (this.state.message) {
             this.setState({ message: null });
         } else {
@@ -58,47 +64,60 @@ class GenericEditor extends React.PureComponent {
         }
     }
 
-    _onChange(e) {
+    protected onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+        // @ts-ignore: Unsure how to convince TS this is okay when the state
+        // type can be extended.
         this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
     }
 
-    _buttons() {
-        return <div className="mx_Dialog_buttons">
+    protected abstract send();
+
+    protected buttons(): React.ReactNode {
+        return <div className="mx_Dialogbuttons">
             <button onClick={this.onBack}>{ _t('Back') }</button>
-            { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
+            { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
         </div>;
     }
 
-    textInput(id, label) {
+    protected textInput(id: string, label: string): React.ReactNode {
         return <Field
             id={id}
             label={label}
-            size="42"
+            size={42}
             autoFocus={true}
             type="text"
             autoComplete="on"
-            value={this.state[id]}
-            onChange={this._onChange}
+            value={this.state[id] as string}
+            onChange={this.onChange}
         />;
     }
 }
 
-export class SendCustomEvent extends GenericEditor {
-    static getLabel() { return _t('Send Custom Event'); }
-
-    static propTypes = {
-        onBack: PropTypes.func.isRequired,
-        room: PropTypes.instanceOf(Room).isRequired,
-        forceStateEvent: PropTypes.bool,
-        forceGeneralEvent: PropTypes.bool,
-        inputs: PropTypes.object,
+interface ISendCustomEventProps extends IGenericEditorProps {
+    room: Room;
+    forceStateEvent?: boolean;
+    forceGeneralEvent?: boolean;
+    inputs?: {
+        eventType?: string;
+        stateKey?: string;
+        evContent?: string;
     };
+}
+
+interface ISendCustomEventState extends IGenericEditorState {
+    isStateEvent: boolean;
+    eventType: string;
+    stateKey: string;
+    evContent: string;
+}
+
+export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendCustomEventState> {
+    static getLabel() { return _t('Send Custom Event'); }
 
     static contextType = MatrixClientContext;
 
     constructor(props) {
         super(props);
-        this._send = this._send.bind(this);
 
         const {eventType, stateKey, evContent} = Object.assign({
             eventType: '',
@@ -115,7 +134,7 @@ export class SendCustomEvent extends GenericEditor {
         };
     }
 
-    send(content) {
+    private doSend(content: object): Promise<void> {
         const cli = this.context;
         if (this.state.isStateEvent) {
             return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey);
@@ -124,7 +143,7 @@ export class SendCustomEvent extends GenericEditor {
         }
     }
 
-    async _send() {
+    protected send = async () => {
         if (this.state.eventType === '') {
             this.setState({ message: _t('You must specify an event type!') });
             return;
@@ -133,7 +152,7 @@ export class SendCustomEvent extends GenericEditor {
         let message;
         try {
             const content = JSON.parse(this.state.evContent);
-            await this.send(content);
+            await this.doSend(content);
             message = _t('Event sent!');
         } catch (e) {
             message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
@@ -147,7 +166,7 @@ export class SendCustomEvent extends GenericEditor {
                 <div className="mx_Dialog_content">
                     { this.state.message }
                 </div>
-                { this._buttons() }
+                { this.buttons() }
             </div>;
         }
 
@@ -163,16 +182,16 @@ export class SendCustomEvent extends GenericEditor {
                 <br />
 
                 <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
-                    autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
+                    autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
-                { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
+                { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
                 { showTglFlip && <div style={{float: "right"}}>
                     <input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
                         type="checkbox"
                         checked={this.state.isStateEvent}
-                        onChange={this._onChange}
+                        onChange={this.onChange}
                     />
                     <label className="mx_DevTools_tgl-btn"
                         data-tg-off="Event"
@@ -185,21 +204,29 @@ export class SendCustomEvent extends GenericEditor {
     }
 }
 
-class SendAccountData extends GenericEditor {
-    static getLabel() { return _t('Send Account Data'); }
-
-    static propTypes = {
-        room: PropTypes.instanceOf(Room).isRequired,
-        isRoomAccountData: PropTypes.bool,
-        forceMode: PropTypes.bool,
-        inputs: PropTypes.object,
+interface ISendAccountDataProps extends IGenericEditorProps {
+    room: Room;
+    isRoomAccountData: boolean;
+    forceMode: boolean;
+    inputs?: {
+        eventType?: string;
+        evContent?: string;
     };
+}
+
+interface ISendAccountDataState extends IGenericEditorState {
+    isRoomAccountData: boolean;
+    eventType: string;
+    evContent: string;
+}
+
+class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountDataState> {
+    static getLabel() { return _t('Send Account Data'); }
 
     static contextType = MatrixClientContext;
 
     constructor(props) {
         super(props);
-        this._send = this._send.bind(this);
 
         const {eventType, evContent} = Object.assign({
             eventType: '',
@@ -214,7 +241,7 @@ class SendAccountData extends GenericEditor {
         };
     }
 
-    send(content) {
+    private doSend(content: object): Promise<void> {
         const cli = this.context;
         if (this.state.isRoomAccountData) {
             return cli.setRoomAccountData(this.props.room.roomId, this.state.eventType, content);
@@ -222,7 +249,7 @@ class SendAccountData extends GenericEditor {
         return cli.setAccountData(this.state.eventType, content);
     }
 
-    async _send() {
+    protected send = async () => {
         if (this.state.eventType === '') {
             this.setState({ message: _t('You must specify an event type!') });
             return;
@@ -231,7 +258,7 @@ class SendAccountData extends GenericEditor {
         let message;
         try {
             const content = JSON.parse(this.state.evContent);
-            await this.send(content);
+            await this.doSend(content);
             message = _t('Event sent!');
         } catch (e) {
             message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
@@ -245,7 +272,7 @@ class SendAccountData extends GenericEditor {
                 <div className="mx_Dialog_content">
                     { this.state.message }
                 </div>
-                { this._buttons() }
+                { this.buttons() }
             </div>;
         }
 
@@ -255,17 +282,17 @@ class SendAccountData extends GenericEditor {
                 <br />
 
                 <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
-                    autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
+                    autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
-                { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
+                { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
                 { !this.state.message && <div style={{float: "right"}}>
                     <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
                         type="checkbox"
                         checked={this.state.isRoomAccountData}
                         disabled={this.props.forceMode}
-                        onChange={this._onChange}
+                        onChange={this.onChange}
                     />
                     <label className="mx_DevTools_tgl-btn"
                         data-tg-off="Account Data"
@@ -281,17 +308,22 @@ class SendAccountData extends GenericEditor {
 const INITIAL_LOAD_TILES = 20;
 const LOAD_TILES_STEP_SIZE = 50;
 
-class FilteredList extends React.PureComponent {
-    static propTypes = {
-        children: PropTypes.any,
-        query: PropTypes.string,
-        onChange: PropTypes.func,
-    };
+interface IFilteredListProps {
+    children: React.ReactElement[];
+    query: string;
+    onChange: (value: string) => void;
+}
 
-    static filterChildren(children, query) {
+interface IFilteredListState {
+    filteredChildren: React.ReactElement[];
+    truncateAt: number;
+}
+
+class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredListState> {
+    static filterChildren(children: React.ReactElement[], query: string): React.ReactElement[] {
         if (!query) return children;
         const lcQuery = query.toLowerCase();
-        return children.filter((child) => child.key.toLowerCase().includes(lcQuery));
+        return children.filter((child) => child.key.toString().toLowerCase().includes(lcQuery));
     }
 
     constructor(props) {
@@ -312,27 +344,27 @@ class FilteredList extends React.PureComponent {
         });
     }
 
-    showAll = () => {
+    private showAll = () => {
         this.setState({
             truncateAt: this.state.truncateAt + LOAD_TILES_STEP_SIZE,
         });
     };
 
-    createOverflowElement = (overflowCount: number, totalCount: number) => {
+    private createOverflowElement = (overflowCount: number, totalCount: number) => {
         return <button className="mx_DevTools_RoomStateExplorer_button" onClick={this.showAll}>
             { _t("and %(count)s others...", { count: overflowCount }) }
         </button>;
     };
 
-    onQuery = (ev) => {
+    private onQuery = (ev: ChangeEvent<HTMLInputElement>) => {
         if (this.props.onChange) this.props.onChange(ev.target.value);
     };
 
-    getChildren = (start: number, end: number) => {
+    private getChildren = (start: number, end: number): React.ReactElement[] => {
         return this.state.filteredChildren.slice(start, end);
     };
 
-    getChildCount = (): number => {
+    private getChildCount = (): number => {
         return this.state.filteredChildren.length;
     };
 
@@ -353,28 +385,31 @@ class FilteredList extends React.PureComponent {
     }
 }
 
-class RoomStateExplorer extends React.PureComponent {
-    static getLabel() { return _t('Explore Room State'); }
+interface IExplorerProps {
+    room: Room;
+    onBack: () => void;
+}
 
-    static propTypes = {
-        onBack: PropTypes.func.isRequired,
-        room: PropTypes.instanceOf(Room).isRequired,
-    };
+interface IRoomStateExplorerState {
+    eventType?: string;
+    event?: MatrixEvent;
+    editing: boolean;
+    queryEventType: string;
+    queryStateKey: string;
+}
+
+class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateExplorerState> {
+    static getLabel() { return _t('Explore Room State'); }
 
     static contextType = MatrixClientContext;
 
-    roomStateEvents: Map<string, Map<string, MatrixEvent>>;
+    private roomStateEvents: Map<string, Map<string, MatrixEvent>>;
 
     constructor(props) {
         super(props);
 
         this.roomStateEvents = this.props.room.currentState.events;
 
-        this.onBack = this.onBack.bind(this);
-        this.editEv = this.editEv.bind(this);
-        this.onQueryEventType = this.onQueryEventType.bind(this);
-        this.onQueryStateKey = this.onQueryStateKey.bind(this);
-
         this.state = {
             eventType: null,
             event: null,
@@ -385,19 +420,19 @@ class RoomStateExplorer extends React.PureComponent {
         };
     }
 
-    browseEventType(eventType) {
+    private browseEventType(eventType: string) {
         return () => {
             this.setState({ eventType });
         };
     }
 
-    onViewSourceClick(event) {
+    private onViewSourceClick(event: MatrixEvent) {
         return () => {
             this.setState({ event });
         };
     }
 
-    onBack() {
+    private onBack = () => {
         if (this.state.editing) {
             this.setState({ editing: false });
         } else if (this.state.event) {
@@ -409,15 +444,15 @@ class RoomStateExplorer extends React.PureComponent {
         }
     }
 
-    editEv() {
+    private editEv = () => {
         this.setState({ editing: true });
     }
 
-    onQueryEventType(filterEventType) {
+    private onQueryEventType = (filterEventType: string) => {
         this.setState({ queryEventType: filterEventType });
     }
 
-    onQueryStateKey(filterStateKey) {
+    private onQueryStateKey = (filterStateKey: string) => {
         this.setState({ queryStateKey: filterStateKey });
     }
 
@@ -437,7 +472,7 @@ class RoomStateExplorer extends React.PureComponent {
                         { JSON.stringify(this.state.event.event, null, 2) }
                     </SyntaxHighlight>
                 </div>
-                <div className="mx_Dialog_buttons">
+                <div className="mx_Dialogbuttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
                     <button onClick={this.editEv}>{ _t('Edit') }</button>
                 </div>
@@ -482,31 +517,29 @@ class RoomStateExplorer extends React.PureComponent {
             <div className="mx_Dialog_content">
                 { list }
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
             </div>
         </div>;
     }
 }
 
-class AccountDataExplorer extends React.PureComponent {
-    static getLabel() { return _t('Explore Account Data'); }
+interface IAccountDataExplorerState {
+    isRoomAccountData: boolean;
+    event?: MatrixEvent;
+    editing: boolean;
+    queryEventType: string;
+    [inputId: string]: boolean | string;
+}
 
-    static propTypes = {
-        onBack: PropTypes.func.isRequired,
-        room: PropTypes.instanceOf(Room).isRequired,
-    };
+class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDataExplorerState> {
+    static getLabel() { return _t('Explore Account Data'); }
 
     static contextType = MatrixClientContext;
 
     constructor(props) {
         super(props);
 
-        this.onBack = this.onBack.bind(this);
-        this.editEv = this.editEv.bind(this);
-        this._onChange = this._onChange.bind(this);
-        this.onQueryEventType = this.onQueryEventType.bind(this);
-
         this.state = {
             isRoomAccountData: false,
             event: null,
@@ -516,20 +549,20 @@ class AccountDataExplorer extends React.PureComponent {
         };
     }
 
-    getData() {
+    private getData(): Record<string, MatrixEvent> {
         if (this.state.isRoomAccountData) {
             return this.props.room.accountData;
         }
         return this.context.store.accountData;
     }
 
-    onViewSourceClick(event) {
+    private onViewSourceClick(event: MatrixEvent) {
         return () => {
             this.setState({ event });
         };
     }
 
-    onBack() {
+    private onBack = () => {
         if (this.state.editing) {
             this.setState({ editing: false });
         } else if (this.state.event) {
@@ -539,15 +572,15 @@ class AccountDataExplorer extends React.PureComponent {
         }
     }
 
-    _onChange(e) {
+    private onChange = (e: ChangeEvent<HTMLInputElement>) => {
         this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
     }
 
-    editEv() {
+    private editEv = () => {
         this.setState({ editing: true });
     }
 
-    onQueryEventType(queryEventType) {
+    private onQueryEventType = (queryEventType: string) => {
         this.setState({ queryEventType });
     }
 
@@ -570,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent {
                         { JSON.stringify(this.state.event.event, null, 2) }
                     </SyntaxHighlight>
                 </div>
-                <div className="mx_Dialog_buttons">
+                <div className="mx_Dialogbuttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
                     <button onClick={this.editEv}>{ _t('Edit') }</button>
                 </div>
@@ -595,40 +628,41 @@ class AccountDataExplorer extends React.PureComponent {
                     { rows }
                 </FilteredList>
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
-                { !this.state.message && <div style={{float: "right"}}>
+                <div style={{float: "right"}}>
                     <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
                         type="checkbox"
                         checked={this.state.isRoomAccountData}
-                        onChange={this._onChange}
+                        onChange={this.onChange}
                     />
                     <label className="mx_DevTools_tgl-btn"
                         data-tg-off="Account Data"
                         data-tg-on="Room Data"
                         htmlFor="isRoomAccountData"
                     />
-                </div> }
+                </div>
             </div>
         </div>;
     }
 }
 
-class ServersInRoomList extends React.PureComponent {
+interface IServersInRoomListState {
+    query: string;
+}
+
+class ServersInRoomList extends React.PureComponent<IExplorerProps, IServersInRoomListState> {
     static getLabel() { return _t('View Servers in Room'); }
 
-    static propTypes = {
-        onBack: PropTypes.func.isRequired,
-        room: PropTypes.instanceOf(Room).isRequired,
-    };
-
     static contextType = MatrixClientContext;
 
+    private servers: React.ReactElement[];
+
     constructor(props) {
         super(props);
 
         const room = this.props.room;
-        const servers = new Set();
+        const servers: Set<string> = new Set();
         room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
         this.servers = Array.from(servers).map(s =>
             <button key={s} className="mx_DevTools_ServersInRoomList_button">
@@ -640,7 +674,7 @@ class ServersInRoomList extends React.PureComponent {
         };
     }
 
-    onQuery = (query) => {
+    private onQuery = (query: string) => {
         this.setState({ query });
     }
 
@@ -651,7 +685,7 @@ class ServersInRoomList extends React.PureComponent {
                     { this.servers }
                 </FilteredList>
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.props.onBack}>{ _t('Back') }</button>
             </div>
         </div>;
@@ -667,7 +701,10 @@ const PHASE_MAP = {
     [PHASE_CANCELLED]: "cancelled",
 };
 
-function VerificationRequest({txnId, request}) {
+const VerificationRequest: React.FC<{
+    txnId: string;
+    request: VerificationRequest;
+}> = ({txnId, request}) => {
     const [, updateState] = useState();
     const [timeout, setRequestTimeout] = useState(request.timeout);
 
@@ -704,7 +741,7 @@ function VerificationRequest({txnId, request}) {
     </div>);
 }
 
-class VerificationExplorer extends React.Component {
+class VerificationExplorer extends React.PureComponent<IExplorerProps> {
     static getLabel() {
         return _t("Verification Requests");
     }
@@ -712,7 +749,7 @@ class VerificationExplorer extends React.Component {
     /* Ensure this.context is the cli */
     static contextType = MatrixClientContext;
 
-    onNewRequest = () => {
+    private onNewRequest = () => {
         this.forceUpdate();
     }
 
@@ -738,14 +775,19 @@ class VerificationExplorer extends React.Component {
                     <VerificationRequest txnId={txnId} request={request} key={txnId} />,
                 )}
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.props.onBack}>{_t("Back")}</button>
             </div>
         </div>);
     }
 }
 
-class WidgetExplorer extends React.Component {
+interface IWidgetExplorerState {
+    query: string;
+    editWidget?: IApp;
+}
+
+class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerState> {
     static getLabel() {
         return _t("Active Widgets");
     }
@@ -759,19 +801,19 @@ class WidgetExplorer extends React.Component {
         };
     }
 
-    onWidgetStoreUpdate = () => {
+    private onWidgetStoreUpdate = () => {
         this.forceUpdate();
     };
 
-    onQueryChange = (query) => {
+    private onQueryChange = (query: string) => {
         this.setState({query});
     };
 
-    onEditWidget = (widget) => {
+    private onEditWidget = (widget: IApp) => {
         this.setState({editWidget: widget});
     };
 
-    onBack = () => {
+    private onBack = () => {
         const widgets = WidgetStore.instance.getApps(this.props.room.roomId);
         if (this.state.editWidget && widgets.includes(this.state.editWidget)) {
             this.setState({editWidget: null});
@@ -794,13 +836,16 @@ class WidgetExplorer extends React.Component {
         const editWidget = this.state.editWidget;
         const widgets = WidgetStore.instance.getApps(room.roomId);
         if (editWidget && widgets.includes(editWidget)) {
-            const allState = Array.from(Array.from(room.currentState.events.values()).map(e => e.values()))
-                .reduce((p, c) => {p.push(...c); return p;}, []);
+            const allState = Array.from(
+                Array.from(room.currentState.events.values()).map((e: Map<string, MatrixEvent>) => {
+                    return e.values();
+                }),
+            ).reduce((p, c) => { p.push(...c); return p; }, []);
             const stateEv = allState.find(ev => ev.getId() === editWidget.eventId);
             if (!stateEv) { // "should never happen"
                 return <div>
                     {_t("There was an error finding this widget.")}
-                    <div className="mx_Dialog_buttons">
+                    <div className="mx_Dialogbuttons">
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
                 </div>;
@@ -829,14 +874,22 @@ class WidgetExplorer extends React.Component {
                     })}
                 </FilteredList>
             </div>
-            <div className="mx_Dialog_buttons">
+            <div className="mx_Dialogbuttons">
                 <button onClick={this.onBack}>{_t("Back")}</button>
             </div>
         </div>);
     }
 }
 
-class SettingsExplorer extends React.Component {
+interface ISettingsExplorerState {
+    query: string;
+    editSetting?: string;
+    viewSetting?: string;
+    explicitValues?: string;
+    explicitRoomValues?: string;
+ }
+
+class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExplorerState> {
     static getLabel() {
         return _t("Settings Explorer");
     }
@@ -854,19 +907,19 @@ class SettingsExplorer extends React.Component {
         };
     }
 
-    onQueryChange = (ev) => {
+    private onQueryChange = (ev: ChangeEvent<HTMLInputElement>) => {
         this.setState({query: ev.target.value});
     };
 
-    onExplValuesEdit = (ev) => {
+    private onExplValuesEdit = (ev: ChangeEvent<HTMLTextAreaElement>) => {
         this.setState({explicitValues: ev.target.value});
     };
 
-    onExplRoomValuesEdit = (ev) => {
+    private onExplRoomValuesEdit = (ev: ChangeEvent<HTMLTextAreaElement>) => {
         this.setState({explicitRoomValues: ev.target.value});
     };
 
-    onBack = () => {
+    private onBack = () => {
         if (this.state.editSetting) {
             this.setState({editSetting: null});
         } else if (this.state.viewSetting) {
@@ -876,12 +929,12 @@ class SettingsExplorer extends React.Component {
         }
     };
 
-    onViewClick = (ev, settingId) => {
+    private onViewClick = (ev: MouseEvent, settingId: string) => {
         ev.preventDefault();
         this.setState({viewSetting: settingId});
     };
 
-    onEditClick = (ev, settingId) => {
+    private onEditClick = (ev: MouseEvent, settingId: string) => {
         ev.preventDefault();
         this.setState({
             editSetting: settingId,
@@ -890,7 +943,7 @@ class SettingsExplorer extends React.Component {
         });
     };
 
-    onSaveClick = async () => {
+    private onSaveClick = async () => {
         try {
             const settingId = this.state.editSetting;
             const parsedExplicit = JSON.parse(this.state.explicitValues);
@@ -899,7 +952,7 @@ class SettingsExplorer extends React.Component {
                 console.log(`[Devtools] Setting value of ${settingId} at ${level} from user input`);
                 try {
                     const val = parsedExplicit[level];
-                    await SettingsStore.setValue(settingId, null, level, val);
+                    await SettingsStore.setValue(settingId, null, level as SettingLevel, val);
                 } catch (e) {
                     console.warn(e);
                 }
@@ -909,7 +962,7 @@ class SettingsExplorer extends React.Component {
                 console.log(`[Devtools] Setting value of ${settingId} at ${level} in ${roomId} from user input`);
                 try {
                     const val = parsedExplicitRoom[level];
-                    await SettingsStore.setValue(settingId, roomId, level, val);
+                    await SettingsStore.setValue(settingId, roomId, level as SettingLevel, val);
                 } catch (e) {
                     console.warn(e);
                 }
@@ -926,7 +979,7 @@ class SettingsExplorer extends React.Component {
         }
     };
 
-    renderSettingValue(val) {
+    private renderSettingValue(val: any): string {
         // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us
         const toStringTypes = ['boolean', 'number'];
         if (toStringTypes.includes(typeof(val))) {
@@ -936,7 +989,7 @@ class SettingsExplorer extends React.Component {
         }
     }
 
-    renderExplicitSettingValues(setting, roomId) {
+    private renderExplicitSettingValues(setting: string, roomId: string): string {
         const vals = {};
         for (const level of LEVEL_ORDER) {
             try {
@@ -951,7 +1004,7 @@ class SettingsExplorer extends React.Component {
         return JSON.stringify(vals, null, 4);
     }
 
-    renderCanEditLevel(roomId, level) {
+    private renderCanEditLevel(roomId: string, level: SettingLevel): React.ReactNode {
         const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level);
         const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable';
         return <td className={className}><code>{canEdit.toString()}</code></td>;
@@ -1006,7 +1059,7 @@ class SettingsExplorer extends React.Component {
                             </tbody>
                         </table>
                     </div>
-                    <div className="mx_Dialog_buttons">
+                    <div className="mx_Dialogbuttons">
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
                 </div>
@@ -1068,7 +1121,7 @@ class SettingsExplorer extends React.Component {
                         </div>
 
                     </div>
-                    <div className="mx_Dialog_buttons">
+                    <div className="mx_Dialogbuttons">
                         <button onClick={this.onSaveClick}>{_t("Save setting values")}</button>
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
@@ -1114,7 +1167,7 @@ class SettingsExplorer extends React.Component {
                         </div>
 
                     </div>
-                    <div className="mx_Dialog_buttons">
+                    <div className="mx_Dialogbuttons">
                         <button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{
                             _t("Edit Values")
                         }</button>
@@ -1126,7 +1179,11 @@ class SettingsExplorer extends React.Component {
     }
 }
 
-const Entries = [
+type DevtoolsDialogEntry = React.JSXElementConstructor<any> & {
+    getLabel: () => string;
+};
+
+const Entries: DevtoolsDialogEntry[] = [
     SendCustomEvent,
     RoomStateExplorer,
     SendAccountData,
@@ -1137,43 +1194,36 @@ const Entries = [
     SettingsExplorer,
 ];
 
-@replaceableComponent("views.dialogs.DevtoolsDialog")
-export default class DevtoolsDialog extends React.PureComponent {
-    static propTypes = {
-        roomId: PropTypes.string.isRequired,
-        onFinished: PropTypes.func.isRequired,
-    };
+interface IProps {
+    roomId: string;
+    onFinished: (finished: boolean) => void;
+}
 
+interface IState {
+    mode?: DevtoolsDialogEntry;
+}
+
+@replaceableComponent("views.dialogs.DevtoolsDialog")
+export default class DevtoolsDialog extends React.PureComponent<IProps, IState> {
     constructor(props) {
         super(props);
-        this.onBack = this.onBack.bind(this);
-        this.onCancel = this.onCancel.bind(this);
 
         this.state = {
             mode: null,
         };
     }
 
-    componentWillUnmount() {
-        this._unmounted = true;
-    }
-
-    _setMode(mode) {
+    private setMode(mode: DevtoolsDialogEntry) {
         return () => {
             this.setState({ mode });
         };
     }
 
-    onBack() {
-        if (this.prevMode) {
-            this.setState({ mode: this.prevMode });
-            this.prevMode = null;
-        } else {
-            this.setState({ mode: null });
-        }
+    private onBack = () => {
+        this.setState({ mode: null });
     }
 
-    onCancel() {
+    private onCancel = () => {
         this.props.onFinished(false);
     }
 
@@ -1200,12 +1250,12 @@ export default class DevtoolsDialog extends React.PureComponent {
                     <div className="mx_Dialog_content">
                         { Entries.map((Entry) => {
                             const label = Entry.getLabel();
-                            const onClick = this._setMode(Entry);
+                            const onClick = this.setMode(Entry);
                             return <button className={classes} key={label} onClick={onClick}>{ label }</button>;
                         }) }
                     </div>
                 </div>
-                <div className="mx_Dialog_buttons">
+                <div className="mx_Dialogbuttons">
                     <button onClick={this.onCancel}>{ _t('Cancel') }</button>
                 </div>
             </React.Fragment>;

From d0da4b2a2578688dc4892ecd68f0f6c0c9317e90 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 12:37:34 +0100
Subject: [PATCH 05/13] Use separate name for verification request component

---
 src/components/views/dialogs/DevtoolsDialog.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 81d3a77327..c4be186da1 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -701,7 +701,7 @@ const PHASE_MAP = {
     [PHASE_CANCELLED]: "cancelled",
 };
 
-const VerificationRequest: React.FC<{
+const VerificationRequestExplorer: React.FC<{
     txnId: string;
     request: VerificationRequest;
 }> = ({txnId, request}) => {
@@ -772,7 +772,7 @@ class VerificationExplorer extends React.PureComponent<IExplorerProps> {
         return (<div>
             <div className="mx_Dialog_content">
                 {Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) =>
-                    <VerificationRequest txnId={txnId} request={request} key={txnId} />,
+                    <VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />,
                 )}
             </div>
             <div className="mx_Dialogbuttons">

From d59b2b357936d4b66595eaea2833996ab81fbd79 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 12:38:32 +0100
Subject: [PATCH 06/13] Fix unintended buttons class change

---
 .../views/dialogs/DevtoolsDialog.tsx          | 30 +++++++++----------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index c4be186da1..7df57b030f 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -73,7 +73,7 @@ abstract class GenericEditor<
     protected abstract send();
 
     protected buttons(): React.ReactNode {
-        return <div className="mx_Dialogbuttons">
+        return <div className="mx_Dialog_buttons">
             <button onClick={this.onBack}>{ _t('Back') }</button>
             { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
         </div>;
@@ -184,7 +184,7 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
                 <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
                     autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
                 { showTglFlip && <div style={{float: "right"}}>
@@ -284,7 +284,7 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
                 <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
                     autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
                 { !this.state.message && <div style={{float: "right"}}>
@@ -472,7 +472,7 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
                         { JSON.stringify(this.state.event.event, null, 2) }
                     </SyntaxHighlight>
                 </div>
-                <div className="mx_Dialogbuttons">
+                <div className="mx_Dialog_buttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
                     <button onClick={this.editEv}>{ _t('Edit') }</button>
                 </div>
@@ -517,7 +517,7 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
             <div className="mx_Dialog_content">
                 { list }
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
             </div>
         </div>;
@@ -603,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
                         { JSON.stringify(this.state.event.event, null, 2) }
                     </SyntaxHighlight>
                 </div>
-                <div className="mx_Dialogbuttons">
+                <div className="mx_Dialog_buttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
                     <button onClick={this.editEv}>{ _t('Edit') }</button>
                 </div>
@@ -628,7 +628,7 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
                     { rows }
                 </FilteredList>
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
                 <div style={{float: "right"}}>
                     <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
@@ -685,7 +685,7 @@ class ServersInRoomList extends React.PureComponent<IExplorerProps, IServersInRo
                     { this.servers }
                 </FilteredList>
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.props.onBack}>{ _t('Back') }</button>
             </div>
         </div>;
@@ -775,7 +775,7 @@ class VerificationExplorer extends React.PureComponent<IExplorerProps> {
                     <VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />,
                 )}
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.props.onBack}>{_t("Back")}</button>
             </div>
         </div>);
@@ -845,7 +845,7 @@ class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerStat
             if (!stateEv) { // "should never happen"
                 return <div>
                     {_t("There was an error finding this widget.")}
-                    <div className="mx_Dialogbuttons">
+                    <div className="mx_Dialog_buttons">
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
                 </div>;
@@ -874,7 +874,7 @@ class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerStat
                     })}
                 </FilteredList>
             </div>
-            <div className="mx_Dialogbuttons">
+            <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{_t("Back")}</button>
             </div>
         </div>);
@@ -1059,7 +1059,7 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
                             </tbody>
                         </table>
                     </div>
-                    <div className="mx_Dialogbuttons">
+                    <div className="mx_Dialog_buttons">
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
                 </div>
@@ -1121,7 +1121,7 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
                         </div>
 
                     </div>
-                    <div className="mx_Dialogbuttons">
+                    <div className="mx_Dialog_buttons">
                         <button onClick={this.onSaveClick}>{_t("Save setting values")}</button>
                         <button onClick={this.onBack}>{_t("Back")}</button>
                     </div>
@@ -1167,7 +1167,7 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
                         </div>
 
                     </div>
-                    <div className="mx_Dialogbuttons">
+                    <div className="mx_Dialog_buttons">
                         <button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{
                             _t("Edit Values")
                         }</button>
@@ -1255,7 +1255,7 @@ export default class DevtoolsDialog extends React.PureComponent<IProps, IState>
                         }) }
                     </div>
                 </div>
-                <div className="mx_Dialogbuttons">
+                <div className="mx_Dialog_buttons">
                     <button onClick={this.onCancel}>{ _t('Cancel') }</button>
                 </div>
             </React.Fragment>;

From f8e61a982b6399f5c2133cf31f5f8d0d01ab0611 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 12:41:59 +0100
Subject: [PATCH 07/13] One less Set

---
 src/components/views/dialogs/DevtoolsDialog.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 7df57b030f..0ea77cc9e8 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -662,7 +662,7 @@ class ServersInRoomList extends React.PureComponent<IExplorerProps, IServersInRo
         super(props);
 
         const room = this.props.room;
-        const servers: Set<string> = new Set();
+        const servers = new Set<string>();
         room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
         this.servers = Array.from(servers).map(s =>
             <button key={s} className="mx_DevTools_ServersInRoomList_button">

From 66077e3a0553871b2a35119b011ff9e6e455c902 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 12:57:51 +0100
Subject: [PATCH 08/13] Add spaces to imports

---
 src/components/views/dialogs/DevtoolsDialog.tsx | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 0ea77cc9e8..0c37ea9599 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, {useState, useEffect, ChangeEvent, MouseEvent} from 'react';
+import React, { useState, useEffect, ChangeEvent, MouseEvent } from 'react';
 import * as sdk from '../../../index';
 import SyntaxHighlight from '../elements/SyntaxHighlight';
 import { _t } from '../../../languageHandler';
 import Field from "../elements/Field";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
 
 import {
     PHASE_UNSENT,
@@ -33,14 +33,14 @@ import {
     VerificationRequest,
 } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
 import WidgetStore, { IApp } from "../../../stores/WidgetStore";
-import {UPDATE_EVENT} from "../../../stores/AsyncStore";
-import {SETTINGS} from "../../../settings/Settings";
-import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore";
+import { UPDATE_EVENT } from "../../../stores/AsyncStore";
+import { SETTINGS } from "../../../settings/Settings";
+import SettingsStore, { LEVEL_ORDER } from "../../../settings/SettingsStore";
 import Modal from "../../../Modal";
 import ErrorDialog from "./ErrorDialog";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
 import { SettingLevel } from '../../../settings/SettingLevel';
 
 interface IGenericEditorProps {

From 5d6f16ff649e05f9780d34b521ceb1a301379f7c Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 12:59:54 +0100
Subject: [PATCH 09/13] Add explicit public

---
 src/components/views/auth/InteractiveAuthEntryComponents.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index 066c064cc1..a61faeea07 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -351,7 +351,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
         this.props.onPhaseChange(DEFAULT_PHASE);
     }
 
-    tryContinue = () => {
+    public tryContinue = () => {
         this.trySubmit();
     };
 

From e4cf66f0e4dc0882efdc99bd6517920d41d9b87a Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 13:09:24 +0100
Subject: [PATCH 10/13] Move state init to constructors

---
 .../auth/InteractiveAuthEntryComponents.tsx   | 40 ++++++++++++++-----
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index a61faeea07..06263a026f 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -113,18 +113,26 @@ interface IAuthEntryProps {
     onPhaseChange: (phase: number) => void;
 }
 
+interface IPasswordAuthEntryState {
+    password: string;
+}
+
 @replaceableComponent("views.auth.PasswordAuthEntry")
-export class PasswordAuthEntry extends React.Component<IAuthEntryProps> {
+export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswordAuthEntryState> {
     static LOGIN_TYPE = AuthType.Password;
 
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            password: "",
+        };
+    }
+
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);
     }
 
-    state = {
-        password: "",
-    };
-
     private onSubmit = (e: FormEvent) => {
         e.preventDefault();
         if (this.props.busy) return;
@@ -483,19 +491,29 @@ interface IMsisdnAuthEntryProps extends IAuthEntryProps {
     fail: (error: Error) => void;
 }
 
+interface IMsisdnAuthEntryState {
+    token: string;
+    requestingToken: boolean;
+    errorText: string;
+}
+
 @replaceableComponent("views.auth.MsisdnAuthEntry")
-export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps> {
+export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
     static LOGIN_TYPE = AuthType.Msisdn;
 
     private submitUrl: string;
     private sid: string;
     private msisdn: string;
 
-    state = {
-        token: '',
-        requestingToken: false,
-        errorText: '',
-    };
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            token: '',
+            requestingToken: false,
+            errorText: '',
+        };
+    }
 
     componentDidMount() {
         this.props.onPhaseChange(DEFAULT_PHASE);

From 320c8d2091afd01362fb23497a5c391acc259b94 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 13:10:50 +0100
Subject: [PATCH 11/13] Move function prop to bottom

---
 src/components/views/auth/InteractiveAuthEntryComponents.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index 06263a026f..7b7ca0061f 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -106,11 +106,11 @@ interface IAuthEntryProps {
     matrixClient: MatrixClient;
     loginType: string;
     authSessionId: string;
-    submitAuthDict: (auth: IAuthDict) => void;
     errorText?: string;
     // Is the auth logic currently waiting for something to happen?
     busy?: boolean;
     onPhaseChange: (phase: number) => void;
+    submitAuthDict: (auth: IAuthDict) => void;
 }
 
 interface IPasswordAuthEntryState {

From 3b50808cb0fffdae177d08e1864a99ba1d07cb97 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 13:11:33 +0100
Subject: [PATCH 12/13] Use any instead of object

---
 .../views/auth/InteractiveAuthEntryComponents.tsx           | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index 7b7ca0061f..b56157491a 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -89,14 +89,14 @@ interface IAuthDict {
     // TODO: Remove `user` once servers support proper UIA
     // See https://github.com/vector-im/element-web/issues/10312
     user?: string;
-    identifier?: object;
+    identifier?: any;
     password?: string;
     response?: string;
     // TODO: Remove `threepid_creds` once servers support proper UIA
     // See https://github.com/vector-im/element-web/issues/10312
     // See https://github.com/matrix-org/matrix-doc/issues/2220
-    threepid_creds?: object;
-    threepidCreds?: object;
+    threepid_creds?: any;
+    threepidCreds?: any;
 }
 /* eslint-enable camelcase */
 

From 44ecf62758f51d752ec1b99121402e4bb177090a Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 21 May 2021 13:15:54 +0100
Subject: [PATCH 13/13] Add explicit member access

---
 .../views/auth/InteractiveAuthEntryComponents.tsx         | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index b56157491a..e819e1e59c 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -689,7 +689,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
         }
     }
 
-    attemptFailed = () => {
+    public attemptFailed = () => {
         this.setState({
             attemptFailed: true,
         });
@@ -704,7 +704,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
         }
     };
 
-    onStartAuthClick = () => {
+    private onStartAuthClick = () => {
         // Note: We don't use PlatformPeg's startSsoAuth functions because we almost
         // certainly will need to open the thing in a new tab to avoid losing application
         // context.
@@ -714,7 +714,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
         this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
     };
 
-    onConfirmClick = () => {
+    private onConfirmClick = () => {
         this.props.submitAuthDict({});
     };
 
@@ -792,7 +792,7 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
         }
     }
 
-    focus = () => {
+    public focus = () => {
         if (this.fallbackButton.current) {
             this.fallbackButton.current.focus();
         }