Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 32aa18f

Browse files
authored
Apply strictNullChecks to src/components/views/auth/* (#10299
* Apply `strictNullChecks` to src/components/views/auth/* * Iterate PR
1 parent c79eff2 commit 32aa18f

16 files changed

+127
-122
lines changed

src/@types/common.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,6 @@ export type RecursivePartial<T> = {
4848
: T[P];
4949
};
5050

51-
// Inspired by https://stackoverflow.com/a/60206860
52-
export type KeysWithObjectShape<Input> = {
53-
[P in keyof Input]: Input[P] extends object
54-
? // Arrays are counted as objects - exclude them
55-
Input[P] extends Array<unknown>
56-
? never
57-
: P
58-
: never;
59-
}[keyof Input];
60-
6151
export type KeysStartingWith<Input extends object, Str extends string> = {
6252
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6353
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X

src/SdkConfig.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Optional } from "matrix-events-sdk";
1919

2020
import { SnakedObject } from "./utils/SnakedObject";
2121
import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
22-
import { KeysWithObjectShape } from "./@types/common";
2322

2423
// see element-web config.md for docs, or the IConfigOptions interface for dev docs
2524
export const DEFAULTS: IConfigOptions = {
@@ -78,10 +77,10 @@ export default class SdkConfig {
7877
return SdkConfig.fallback.get(key, altCaseName);
7978
}
8079

81-
public static getObject<K extends KeysWithObjectShape<IConfigOptions>>(
80+
public static getObject<K extends keyof IConfigOptions>(
8281
key: K,
8382
altCaseName?: string,
84-
): Optional<SnakedObject<IConfigOptions[K]>> {
83+
): Optional<SnakedObject<NonNullable<IConfigOptions[K]>>> {
8584
const val = SdkConfig.get(key, altCaseName);
8685
if (val !== null && val !== undefined) {
8786
return new SnakedObject(val);

src/components/structures/InteractiveAuth.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ interface IProps {
8080
// Called when the stage changes, or the stage's phase changes. First
8181
// argument is the stage, second is the phase. Some stages do not have
8282
// phases and will be counted as 0 (numeric).
83-
onStagePhaseChange?(stage: AuthType, phase: number): void;
83+
onStagePhaseChange?(stage: AuthType | null, phase: number): void;
8484
}
8585

8686
interface IState {
@@ -170,7 +170,8 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
170170
busy: true,
171171
});
172172
try {
173-
return await this.props.requestEmailToken(email, secret, attempt, session);
173+
// We know this method only gets called on flows where requestEmailToken is passed but types don't
174+
return await this.props.requestEmailToken!(email, secret, attempt, session);
174175
} finally {
175176
this.setState({
176177
busy: false,
@@ -231,7 +232,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
231232
};
232233

233234
private onPhaseChange = (newPhase: number): void => {
234-
this.props.onStagePhaseChange?.(this.state.authStage, newPhase || 0);
235+
this.props.onStagePhaseChange?.(this.state.authStage ?? null, newPhase || 0);
235236
};
236237

237238
private onStageCancel = (): void => {

src/components/structures/MatrixChat.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2015,7 +2015,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
20152015

20162016
public render(): React.ReactNode {
20172017
const fragmentAfterLogin = this.getFragmentAfterLogin();
2018-
let view = null;
2018+
let view: JSX.Element;
20192019

20202020
if (this.state.view === Views.LOADING) {
20212021
view = (
@@ -2132,6 +2132,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
21322132
view = <UseCaseSelection onFinished={(useCase): Promise<void> => this.onShowPostLoginScreen(useCase)} />;
21332133
} else {
21342134
logger.error(`Unknown view ${this.state.view}`);
2135+
return null;
21352136
}
21362137

21372138
return (

src/components/structures/auth/Registration.tsx

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,29 @@ interface IProps {
7878
}
7979

8080
interface IState {
81+
// true if we're waiting for the user to complete
8182
busy: boolean;
8283
errorText?: ReactNode;
83-
// true if we're waiting for the user to complete
8484
// We remember the values entered by the user because
8585
// the registration form will be unmounted during the
8686
// course of registration, but if there's an error we
8787
// want to bring back the registration form with the
8888
// values the user entered still in it. We can keep
8989
// them in this component's state since this component
9090
// persist for the duration of the registration process.
91-
formVals: Record<string, string>;
91+
formVals: Record<string, string | undefined>;
9292
// user-interactive auth
9393
// If we've been given a session ID, we're resuming
9494
// straight back into UI auth
9595
doingUIAuth: boolean;
9696
// If set, we've registered but are not going to log
9797
// the user in to their new account automatically.
9898
completedNoSignin: boolean;
99-
flows: {
100-
stages: string[];
101-
}[];
99+
flows:
100+
| {
101+
stages: string[];
102+
}[]
103+
| null;
102104
// We perform liveliness checks later, but for now suppress the errors.
103105
// We also track the server dead errors independently of the regular errors so
104106
// that we can render it differently, and override any other error the user may
@@ -158,7 +160,7 @@ export default class Registration extends React.Component<IProps, IState> {
158160
window.removeEventListener("beforeunload", this.unloadCallback);
159161
}
160162

161-
private unloadCallback = (event: BeforeUnloadEvent): string => {
163+
private unloadCallback = (event: BeforeUnloadEvent): string | undefined => {
162164
if (this.state.doingUIAuth) {
163165
event.preventDefault();
164166
event.returnValue = "";
@@ -215,7 +217,7 @@ export default class Registration extends React.Component<IProps, IState> {
215217
this.loginLogic.setHomeserverUrl(hsUrl);
216218
this.loginLogic.setIdentityServerUrl(isUrl);
217219

218-
let ssoFlow: ISSOFlow;
220+
let ssoFlow: ISSOFlow | undefined;
219221
try {
220222
const loginFlows = await this.loginLogic.getFlows();
221223
if (serverConfig !== this.latestServerConfig) return; // discard, serverConfig changed from under us
@@ -289,6 +291,7 @@ export default class Registration extends React.Component<IProps, IState> {
289291
sendAttempt: number,
290292
sessionId: string,
291293
): Promise<IRequestTokenResponse> => {
294+
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
292295
return this.state.matrixClient.requestRegisterEmailToken(
293296
emailAddress,
294297
clientSecret,
@@ -303,6 +306,8 @@ export default class Registration extends React.Component<IProps, IState> {
303306
};
304307

305308
private onUIAuthFinished: InteractiveAuthCallback = async (success, response): Promise<void> => {
309+
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
310+
306311
debuglog("Registration: ui authentication finished: ", { success, response });
307312
if (!success) {
308313
let errorText: ReactNode = (response as Error).message || (response as Error).toString();
@@ -327,10 +332,8 @@ export default class Registration extends React.Component<IProps, IState> {
327332
</div>
328333
);
329334
} else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) {
330-
let msisdnAvailable = false;
331-
for (const flow of (response as IAuthData).available_flows) {
332-
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
333-
}
335+
const flows = (response as IAuthData).available_flows ?? [];
336+
const msisdnAvailable = flows.some((flow) => flow.stages.includes(AuthType.Msisdn));
334337
if (!msisdnAvailable) {
335338
errorText = _t("This server does not support authentication with a phone number.");
336339
}
@@ -348,12 +351,16 @@ export default class Registration extends React.Component<IProps, IState> {
348351
return;
349352
}
350353

351-
MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id);
354+
const userId = (response as IAuthData).user_id;
355+
const accessToken = (response as IAuthData).access_token;
356+
if (!userId || !accessToken) throw new Error("Registration failed");
357+
358+
MatrixClientPeg.setJustRegisteredUserId(userId);
352359

353360
const newState: Partial<IState> = {
354361
doingUIAuth: false,
355362
registeredUsername: (response as IAuthData).user_id,
356-
differentLoggedInUserId: null,
363+
differentLoggedInUserId: undefined,
357364
completedNoSignin: false,
358365
// we're still busy until we get unmounted: don't show the registration form again
359366
busy: true,
@@ -393,13 +400,13 @@ export default class Registration extends React.Component<IProps, IState> {
393400
// the email, not the client that started the registration flow
394401
await this.props.onLoggedIn(
395402
{
396-
userId: (response as IAuthData).user_id,
403+
userId,
397404
deviceId: (response as IAuthData).device_id,
398405
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
399406
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
400-
accessToken: (response as IAuthData).access_token,
407+
accessToken,
401408
},
402-
this.state.formVals.password,
409+
this.state.formVals.password!,
403410
);
404411

405412
this.setupPushers();
@@ -457,6 +464,8 @@ export default class Registration extends React.Component<IProps, IState> {
457464
};
458465

459466
private makeRegisterRequest = (auth: IAuthData | null): Promise<IAuthData> => {
467+
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
468+
460469
const registerParams: IRegisterRequestParams = {
461470
username: this.state.formVals.username,
462471
password: this.state.formVals.password,
@@ -494,7 +503,7 @@ export default class Registration extends React.Component<IProps, IState> {
494503
return sessionLoaded;
495504
};
496505

497-
private renderRegisterComponent(): JSX.Element {
506+
private renderRegisterComponent(): ReactNode {
498507
if (this.state.matrixClient && this.state.doingUIAuth) {
499508
return (
500509
<InteractiveAuth
@@ -517,8 +526,8 @@ export default class Registration extends React.Component<IProps, IState> {
517526
<Spinner />
518527
</div>
519528
);
520-
} else if (this.state.flows.length) {
521-
let ssoSection;
529+
} else if (this.state.matrixClient && this.state.flows.length) {
530+
let ssoSection: JSX.Element | undefined;
522531
if (this.state.ssoFlow) {
523532
let continueWithSection;
524533
const providers = this.state.ssoFlow.identity_providers || [];
@@ -571,6 +580,8 @@ export default class Registration extends React.Component<IProps, IState> {
571580
</React.Fragment>
572581
);
573582
}
583+
584+
return null;
574585
}
575586

576587
public render(): React.ReactNode {

src/components/views/auth/InteractiveAuthEntryComponents.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const DEFAULT_PHASE = 0;
8383
interface IAuthEntryProps {
8484
matrixClient: MatrixClient;
8585
loginType: string;
86-
authSessionId: string;
86+
authSessionId?: string;
8787
errorText?: string;
8888
errorCode?: string;
8989
// Is the auth logic currently waiting for something to happen?
@@ -120,7 +120,7 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
120120
type: AuthType.Password,
121121
// TODO: Remove `user` once servers support proper UIA
122122
// See https://github.com/vector-im/element-web/issues/10312
123-
user: this.props.matrixClient.credentials.userId,
123+
user: this.props.matrixClient.credentials.userId ?? undefined,
124124
identifier: {
125125
type: "m.id.user",
126126
user: this.props.matrixClient.credentials.userId,
@@ -286,7 +286,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
286286
// }
287287
// }
288288

289-
const allPolicies = this.props.stageParams.policies || {};
289+
const allPolicies = this.props.stageParams?.policies || {};
290290
const prefLang = SettingsStore.getValue("language");
291291
const initToggles: Record<string, boolean> = {};
292292
const pickedPolicies: {
@@ -300,12 +300,12 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
300300
// Pick a language based on the user's language, falling back to english,
301301
// and finally to the first language available. If there's still no policy
302302
// available then the homeserver isn't respecting the spec.
303-
let langPolicy = policy[prefLang];
303+
let langPolicy: LocalisedPolicy | undefined = policy[prefLang];
304304
if (!langPolicy) langPolicy = policy["en"];
305305
if (!langPolicy) {
306306
// last resort
307307
const firstLang = Object.keys(policy).find((e) => e !== "version");
308-
langPolicy = policy[firstLang];
308+
langPolicy = firstLang ? policy[firstLang] : undefined;
309309
}
310310
if (!langPolicy) throw new Error("Failed to find a policy to show the user");
311311

@@ -358,7 +358,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
358358
return <Spinner />;
359359
}
360360

361-
const checkboxes = [];
361+
const checkboxes: JSX.Element[] = [];
362362
let allChecked = true;
363363
for (const policy of this.state.policies) {
364364
const checked = this.state.toggledPolicies[policy.id];
@@ -384,7 +384,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
384384
);
385385
}
386386

387-
let submitButton;
387+
let submitButton: JSX.Element | undefined;
388388
if (this.props.showContinue !== false) {
389389
// XXX: button classes
390390
submitButton = (
@@ -462,7 +462,7 @@ export class EmailIdentityAuthEntry extends React.Component<
462462
// We only have a session ID if the user has clicked the link in their email,
463463
// so show a loading state instead of "an email has been sent to..." because
464464
// that's confusing when you've already read that email.
465-
if (this.props.inputs.emailAddress === undefined || this.props.stageState?.emailSid) {
465+
if (this.props.inputs?.emailAddress === undefined || this.props.stageState?.emailSid) {
466466
if (errorSection) {
467467
return errorSection;
468468
}
@@ -549,13 +549,13 @@ interface IMsisdnAuthEntryProps extends IAuthEntryProps {
549549
interface IMsisdnAuthEntryState {
550550
token: string;
551551
requestingToken: boolean;
552-
errorText: string;
552+
errorText: string | null;
553553
}
554554

555555
export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
556556
public static LOGIN_TYPE = AuthType.Msisdn;
557557

558-
private submitUrl: string;
558+
private submitUrl?: string;
559559
private sid: string;
560560
private msisdn: string;
561561

@@ -798,11 +798,13 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
798798
public static PHASE_POSTAUTH = 2; // button to confirm SSO completed
799799

800800
private ssoUrl: string;
801-
private popupWindow: Window;
801+
private popupWindow: Window | null;
802802

803803
public constructor(props: ISSOAuthEntryProps) {
804804
super(props);
805805

806+
if (!this.props.authSessionId) throw new Error("This UIA flow requires an authSessionId");
807+
806808
// We actually send the user through fallback auth so we don't have to
807809
// deal with a redirect back to us, losing application context.
808810
this.ssoUrl = props.matrixClient.getFallbackAuthUrl(this.props.loginType, this.props.authSessionId);
@@ -858,10 +860,10 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
858860
};
859861

860862
public render(): React.ReactNode {
861-
let continueButton = null;
863+
let continueButton: JSX.Element;
862864
const cancelButton = (
863865
<AccessibleButton
864-
onClick={this.props.onCancel}
866+
onClick={this.props.onCancel ?? null}
865867
kind={this.props.continueKind ? this.props.continueKind + "_outline" : "primary_outline"}
866868
>
867869
{_t("Cancel")}
@@ -909,7 +911,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
909911
}
910912

911913
export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
912-
private popupWindow: Window;
914+
private popupWindow: Window | null;
913915
private fallbackButton = createRef<HTMLButtonElement>();
914916

915917
public constructor(props: IAuthEntryProps) {
@@ -927,18 +929,16 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
927929

928930
public componentWillUnmount(): void {
929931
window.removeEventListener("message", this.onReceiveMessage);
930-
if (this.popupWindow) {
931-
this.popupWindow.close();
932-
}
932+
this.popupWindow?.close();
933933
}
934934

935935
public focus = (): void => {
936-
if (this.fallbackButton.current) {
937-
this.fallbackButton.current.focus();
938-
}
936+
this.fallbackButton.current?.focus();
939937
};
940938

941939
private onShowFallbackClick = (e: MouseEvent): void => {
940+
if (!this.props.authSessionId) return;
941+
942942
e.preventDefault();
943943
e.stopPropagation();
944944

src/components/views/auth/LanguageSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import LanguageDropdown from "../elements/LanguageDropdown";
2626
function onChange(newLang: string): void {
2727
if (getCurrentLanguage() !== newLang) {
2828
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang);
29-
PlatformPeg.get().reload();
29+
PlatformPeg.get()?.reload();
3030
}
3131
}
3232

0 commit comments

Comments
 (0)