From 6a3f59cc7603cf658e59bdf2c168b64644a74523 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 May 2023 14:25:43 +0100 Subject: [PATCH] Make more of the codebase conform to strict types (#10857) --- src/AddThreepid.ts | 2 +- src/ContentMessages.ts | 4 ++-- src/IdentityAuthClient.tsx | 4 ++-- src/Notifier.ts | 4 +++- src/SlidingSyncManager.ts | 6 +++--- src/accessibility/KeyboardShortcutUtils.ts | 4 ++-- .../security/CreateSecretStorageDialog.tsx | 13 +++++++++---- src/boundThreepids.ts | 3 ++- src/components/structures/FilePanel.tsx | 2 +- .../structures/GenericDropdownMenu.tsx | 2 +- src/components/structures/ScrollPanel.tsx | 14 +++++++------- .../structures/auth/CompleteSecurity.tsx | 2 +- src/components/structures/auth/Login.tsx | 6 +++--- .../structures/auth/SetupEncryptionBody.tsx | 2 +- src/components/structures/auth/SoftLogout.tsx | 7 ++++++- .../auth/InteractiveAuthEntryComponents.tsx | 8 +++++--- .../dialogs/ConfirmSpaceUserActionDialog.tsx | 4 ++-- src/components/views/dialogs/InfoDialog.tsx | 4 ++-- .../views/dialogs/MessageEditHistoryDialog.tsx | 2 +- .../views/dialogs/ServerPickerDialog.tsx | 4 ++-- .../views/dialogs/devtools/Event.tsx | 5 +++-- .../dialogs/security/SetupEncryptionDialog.tsx | 2 +- .../dialogs/spotlight/SpotlightDialog.tsx | 16 +++------------- .../views/elements/AppPermission.tsx | 4 ++-- src/components/views/elements/AppTile.tsx | 2 +- src/components/views/elements/Field.tsx | 10 ++++++---- .../views/elements/InteractiveTooltip.tsx | 8 ++++++-- .../views/rooms/BasicMessageComposer.tsx | 6 +++--- .../views/rooms/MessageComposerButtons.tsx | 5 +++-- src/components/views/rooms/RoomSublist.tsx | 4 ++-- src/components/views/rooms/SearchBar.tsx | 2 +- .../views/rooms/SendMessageComposer.tsx | 2 +- .../tabs/user/KeyboardUserSettingsTab.tsx | 4 ++-- src/editor/dom.ts | 12 ++++++++---- src/editor/history.ts | 12 ++++++------ src/hooks/usePublicRoomDirectory.ts | 2 +- src/indexing/EventIndex.ts | 5 +++-- src/stores/SetupEncryptionStore.ts | 2 +- src/theme.ts | 4 ++-- src/utils/IdentityServerUtils.ts | 3 ++- src/utils/Timer.ts | 18 ++++++++---------- src/utils/beacon/useOwnLiveBeacons.ts | 2 +- test/editor/mock.ts | 4 ++-- test/editor/position-test.ts | 13 +------------ test/stores/SetupEncryptionStore-test.ts | 4 ++-- 45 files changed, 127 insertions(+), 121 deletions(-) diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index fdf0fc65f1..399c0d9883 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -56,7 +56,7 @@ export default class AddThreepid { private sessionId: string; private submitUrl?: string; private clientSecret: string; - private bind: boolean; + private bind = false; public constructor() { this.clientSecret = MatrixClientPeg.get().generateClientSecret(); diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index 44eecacb7f..2764f5fa2d 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -580,13 +580,13 @@ export default class ContentMessages { } catch (error) { // 413: File was too big or upset the server in some way: // clear the media size limit so we fetch it again next time we try to upload - if (error?.httpStatus === 413) { + if (error instanceof HTTPError && error.httpStatus === 413) { this.mediaConfig = null; } if (!upload.cancelled) { let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName }); - if (error.httpStatus === 413) { + if (error instanceof HTTPError && error.httpStatus === 413) { desc = _t("The file '%(fileName)s' exceeds this homeserver's size limit for uploads", { fileName: upload.fileName, }); diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx index 5ad918d0a3..0eed6fb7f8 100644 --- a/src/IdentityAuthClient.tsx +++ b/src/IdentityAuthClient.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from "react"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; -import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { createClient, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "./MatrixClientPeg"; @@ -123,7 +123,7 @@ export default class IdentityAuthClient { try { await this.matrixClient.getIdentityAccount(token); } catch (e) { - if (e.errcode === "M_TERMS_NOT_SIGNED") { + if (e instanceof MatrixError && e.errcode === "M_TERMS_NOT_SIGNED") { logger.log("Identity server requires new terms to be agreed to"); await startTermsFlow([new Service(SERVICE_TYPES.IS, identityServerUrl, token)]); return; diff --git a/src/Notifier.ts b/src/Notifier.ts index d47a9c871b..96abb960ad 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -132,7 +132,7 @@ class NotifierClass { let msg = this.notificationMessageForEvent(ev); if (!msg) return; - let title; + let title: string | undefined; if (!ev.sender || room.name === ev.sender.name) { title = room.name; // notificationMessageForEvent includes sender, but we already have the sender here @@ -153,6 +153,8 @@ class NotifierClass { } } + if (!title) return; + if (!this.isBodyEnabled()) { msg = ""; } diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts index 4a6c113253..9af442932e 100644 --- a/src/SlidingSyncManager.ts +++ b/src/SlidingSyncManager.ts @@ -118,7 +118,7 @@ export class SlidingSyncManager { private static readonly internalInstance = new SlidingSyncManager(); public slidingSync: SlidingSync; - private client: MatrixClient; + private client?: MatrixClient; private configureDefer: IDeferred; @@ -242,8 +242,8 @@ export class SlidingSyncManager { } else { subscriptions.delete(roomId); } - const room = this.client.getRoom(roomId); - let shouldLazyLoad = !this.client.isRoomEncrypted(roomId); + const room = this.client?.getRoom(roomId); + let shouldLazyLoad = !this.client?.isRoomEncrypted(roomId); if (!room) { // default to safety: request all state if we can't work it out. This can happen if you // refresh the app whilst viewing a room: we call setRoomVisible before we know anything diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts index 8ba866be3f..acbf14d756 100644 --- a/src/accessibility/KeyboardShortcutUtils.ts +++ b/src/accessibility/KeyboardShortcutUtils.ts @@ -95,8 +95,8 @@ const getUIOnlyShortcuts = (): IKeyboardShortcuts => { export const getKeyboardShortcuts = (): IKeyboardShortcuts => { const overrideBrowserShortcuts = PlatformPeg.get()?.overrideBrowserShortcuts(); - return Object.keys(KEYBOARD_SHORTCUTS) - .filter((k: KeyBindingAction) => { + return (Object.keys(KEYBOARD_SHORTCUTS) as KeyBindingAction[]) + .filter((k) => { if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false; if (MAC_ONLY_SHORTCUTS.includes(k) && !IS_MAC) return false; if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false; diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index 3e48739826..35b90c484c 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -20,7 +20,7 @@ import FileSaver from "file-saver"; import { logger } from "matrix-js-sdk/src/logger"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import { TrustInfo } from "matrix-js-sdk/src/crypto/backup"; -import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix"; +import { CrossSigningKeys, MatrixError, UIAFlow } from "matrix-js-sdk/src/matrix"; import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import classNames from "classnames"; @@ -103,7 +103,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent(); private passphraseField = createRef(); @@ -208,7 +208,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // This is used to track if a decrypted event was a live event and should be // added to the timeline. private decryptingEvents = new Set(); - public noRoom: boolean; + public noRoom = false; private card = createRef(); public state: IState = { diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx index 27dd60e107..0a38db3dbe 100644 --- a/src/components/structures/GenericDropdownMenu.tsx +++ b/src/components/structures/GenericDropdownMenu.tsx @@ -125,7 +125,7 @@ export function GenericDropdownMenu({ }: IProps): JSX.Element { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); - const selected: GenericDropdownMenuItem | null = options + const selected: GenericDropdownMenuItem | undefined = options .flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) .find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value)); let contextMenuOptions: JSX.Element; diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 61279cffb3..63022d80c3 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -181,19 +181,19 @@ export default class ScrollPanel extends React.Component { private unmounted = false; private scrollTimeout?: Timer; // Are we currently trying to backfill? - private isFilling: boolean; + private isFilling = false; // Is the current fill request caused by a props update? private isFillingDueToPropsUpdate = false; // Did another request to check the fill state arrive while we were trying to backfill? - private fillRequestWhileRunning: boolean; + private fillRequestWhileRunning = false; // Is that next fill request scheduled because of a props update? - private pendingFillDueToPropsUpdate: boolean; - private scrollState: IScrollState; + private pendingFillDueToPropsUpdate = false; + private scrollState!: IScrollState; private preventShrinkingState: IPreventShrinkingState | null = null; private unfillDebouncer: number | null = null; - private bottomGrowth: number; - private minListHeight: number; - private heightUpdateInProgress: boolean; + private bottomGrowth!: number; + private minListHeight!: number; + private heightUpdateInProgress = false; private divScroll: HTMLDivElement | null = null; public constructor(props: IProps) { diff --git a/src/components/structures/auth/CompleteSecurity.tsx b/src/components/structures/auth/CompleteSecurity.tsx index 3171a0ec88..23fcffa145 100644 --- a/src/components/structures/auth/CompleteSecurity.tsx +++ b/src/components/structures/auth/CompleteSecurity.tsx @@ -28,7 +28,7 @@ interface IProps { } interface IState { - phase: Phase; + phase?: Phase; lostKeys: boolean; } diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 015ed6bbee..2fb46cc0ee 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -19,7 +19,7 @@ import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; -import { _t, _td } from "../../../languageHandler"; +import { _t, _td, UserFriendlyError } from "../../../languageHandler"; import Login from "../../../Login"; import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; @@ -110,7 +110,7 @@ type OnPasswordLogin = { */ export default class LoginComponent extends React.PureComponent { private unmounted = false; - private loginLogic: Login; + private loginLogic!: Login; private readonly stepRendererMap: Record ReactNode>; @@ -265,7 +265,7 @@ export default class LoginComponent extends React.PureComponent logger.error("Problem parsing URL or unhandled error doing .well-known discovery:", e); let message = _t("Failed to perform homeserver discovery"); - if (e.translatedMessage) { + if (e instanceof UserFriendlyError && e.translatedMessage) { message = e.translatedMessage; } diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index d94718ab60..4be5efa5c9 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -38,7 +38,7 @@ interface IProps { } interface IState { - phase: Phase; + phase?: Phase; verificationRequest: VerificationRequest | null; backupInfo: IKeyBackupInfo | null; lostKeys: boolean; diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index a7a377e1d1..f8a058fc7b 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -18,6 +18,7 @@ import React, { ChangeEvent, SyntheticEvent } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; +import { MatrixError } from "matrix-js-sdk/src/http-api"; import { _t } from "../../../languageHandler"; import dis from "../../../dispatcher/dispatcher"; @@ -164,7 +165,11 @@ export default class SoftLogout extends React.Component { credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams); } catch (e) { let errorText = _t("Failed to re-authenticate due to a homeserver problem"); - if (e.errcode === "M_FORBIDDEN" && (e.httpStatus === 401 || e.httpStatus === 403)) { + if ( + e instanceof MatrixError && + e.errcode === "M_FORBIDDEN" && + (e.httpStatus === 401 || e.httpStatus === 403) + ) { errorText = _t("Incorrect password"); } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index cefdecab5e..94180f40eb 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -214,7 +214,7 @@ export class RecaptchaAuthEntry extends React.Component @@ -235,7 +235,9 @@ export class RecaptchaAuthEntry extends React.Component - + {sitePublicKey && ( + + )} {errorSection} ); diff --git a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx index e4d1783429..59597ad66a 100644 --- a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx +++ b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx @@ -28,7 +28,7 @@ interface IProps extends Omit = ({ return ( { + onFinished={(success?: boolean, reason?: string) => { onFinished(success, reason, roomsToLeave); }} className="mx_ConfirmSpaceUserActionDialog" diff --git a/src/components/views/dialogs/InfoDialog.tsx b/src/components/views/dialogs/InfoDialog.tsx index 790c77d418..d218438f62 100644 --- a/src/components/views/dialogs/InfoDialog.tsx +++ b/src/components/views/dialogs/InfoDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode, KeyboardEvent } from "react"; +import React, { ReactNode } from "react"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; @@ -30,7 +30,7 @@ interface IProps { button?: boolean | string; hasCloseButton?: boolean; fixedWidth?: boolean; - onKeyDown?(event: KeyboardEvent): void; + onKeyDown?(event: KeyboardEvent | React.KeyboardEvent): void; onFinished(): void; } diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/src/components/views/dialogs/MessageEditHistoryDialog.tsx index 197709b8b7..a8b446df09 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.tsx +++ b/src/components/views/dialogs/MessageEditHistoryDialog.tsx @@ -76,7 +76,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent reject(error)); diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 5a06db2da6..4b6b17c01d 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -20,7 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import BaseDialog from "./BaseDialog"; -import { _t } from "../../../languageHandler"; +import { _t, UserFriendlyError } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import SdkConfig from "../../../SdkConfig"; import Field from "../elements/Field"; @@ -113,7 +113,7 @@ export default class ServerPickerDialog extends React.PureComponent ({ }); const validateEventContent = withValidation({ - deriveData({ value }) { + async deriveData({ value }) { try { JSON.parse(value!); } catch (e) { - return e; + return e as Error; } + return undefined; }, rules: [ { diff --git a/src/components/views/dialogs/security/SetupEncryptionDialog.tsx b/src/components/views/dialogs/security/SetupEncryptionDialog.tsx index 659b9cc9e7..a69a178e66 100644 --- a/src/components/views/dialogs/security/SetupEncryptionDialog.tsx +++ b/src/components/views/dialogs/security/SetupEncryptionDialog.tsx @@ -21,7 +21,7 @@ import BaseDialog from "../BaseDialog"; import { _t } from "../../../../languageHandler"; import { SetupEncryptionStore, Phase } from "../../../../stores/SetupEncryptionStore"; -function iconFromPhase(phase: Phase): string { +function iconFromPhase(phase?: Phase): string { if (phase === Phase.Done) { return require("../../../../../res/img/e2e/verified-deprecated.svg").default; } else { diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 94db40847b..759250651a 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -21,17 +21,7 @@ import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/models/room"; import { normalize } from "matrix-js-sdk/src/utils"; -import React, { - ChangeEvent, - KeyboardEvent, - RefObject, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import sanitizeHtml from "sanitize-html"; import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; @@ -1067,7 +1057,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n ); } - const onDialogKeyDown = (ev: KeyboardEvent): void => { + const onDialogKeyDown = (ev: KeyboardEvent | React.KeyboardEvent): void => { const navigationAction = getKeyBindingsManager().getNavigationAction(ev); switch (navigationAction) { case KeyBindingAction.FilterRooms: @@ -1139,7 +1129,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n } }; - const onKeyDown = (ev: KeyboardEvent): void => { + const onKeyDown = (ev: React.KeyboardEvent): void => { const action = getKeyBindingsManager().getAccessibilityAction(ev); switch (action) { diff --git a/src/components/views/elements/AppPermission.tsx b/src/components/views/elements/AppPermission.tsx index 2c1015bd1a..86b6a28b8a 100644 --- a/src/components/views/elements/AppPermission.tsx +++ b/src/components/views/elements/AppPermission.tsx @@ -38,7 +38,7 @@ interface IProps { } interface IState { - roomMember: RoomMember; + roomMember: RoomMember | null; isWrapped: boolean; widgetDomain: string | null; } @@ -56,7 +56,7 @@ export default class AppPermission extends React.Component { // The second step is to find the user's profile so we can show it on the prompt const room = MatrixClientPeg.get().getRoom(this.props.roomId); - let roomMember; + let roomMember: RoomMember | null = null; if (room) roomMember = room.getMember(this.props.creatorUserId); // Set all this into the initial state diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index a90e01f317..b132336fb6 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -126,7 +126,7 @@ export default class AppTile extends React.Component { private persistKey: string; private sgWidget: StopGapWidget | null; private dispatcherRef?: string; - private unmounted: boolean; + private unmounted = false; public constructor(props: IProps) { super(props); diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index a453ffbee5..f76b945712 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject } from "react"; +import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject, createRef } from "react"; import classNames from "classnames"; import { debounce } from "lodash"; @@ -118,7 +118,7 @@ interface IState { export default class Field extends React.PureComponent { private readonly id: string; - private inputRef: RefObject; + private readonly _inputRef = createRef(); public static readonly defaultProps = { element: "input", @@ -228,6 +228,10 @@ export default class Field extends React.PureComponent { return valid; } + private get inputRef(): RefObject { + return this.props.inputRef ?? this._inputRef; + } + public render(): React.ReactNode { /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ const { @@ -249,8 +253,6 @@ export default class Field extends React.PureComponent { ...inputProps } = this.props; - this.inputRef = inputRef || React.createRef(); - // Handle displaying feedback on validity let fieldTooltip: JSX.Element | undefined; if (tooltipContent || this.state.feedback) { diff --git a/src/components/views/elements/InteractiveTooltip.tsx b/src/components/views/elements/InteractiveTooltip.tsx index c862f06d95..3428f3ef2d 100644 --- a/src/components/views/elements/InteractiveTooltip.tsx +++ b/src/components/views/elements/InteractiveTooltip.tsx @@ -302,7 +302,7 @@ interface IState { * tooltip along one edge of the target. */ export default class InteractiveTooltip extends React.Component { - private target: HTMLElement; + private target?: HTMLElement; public static defaultProps = { side: Direction.Top, @@ -345,6 +345,7 @@ export default class InteractiveTooltip extends React.Component private onLeftOfTarget(): boolean { const { contentRect } = this.state; + if (!this.target) return false; const targetRect = this.target.getBoundingClientRect(); if (this.props.direction === Direction.Left) { @@ -359,6 +360,7 @@ export default class InteractiveTooltip extends React.Component private aboveTarget(): boolean { const { contentRect } = this.state; + if (!this.target) return false; const targetRect = this.target.getBoundingClientRect(); if (this.props.direction === Direction.Top) { @@ -378,7 +380,7 @@ export default class InteractiveTooltip extends React.Component private onMouseMove = (ev: MouseEvent): void => { const { clientX: x, clientY: y } = ev; const { contentRect } = this.state; - if (!contentRect) return; + if (!contentRect || !this.target) return; const targetRect = this.target.getBoundingClientRect(); let direction: Direction; @@ -423,6 +425,8 @@ export default class InteractiveTooltip extends React.Component return null; } + if (!this.target) return null; + const targetRect = this.target.getBoundingClientRect(); // The window X and Y offsets are to adjust position when zoomed in to page diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index a6c695e9ca..a197373ef8 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -107,7 +107,7 @@ interface IProps { initialCaret?: DocumentOffset; disabled?: boolean; - onChange?(selection: Caret, inputType?: string, diff?: IDiff): void; + onChange?(selection?: Caret, inputType?: string, diff?: IDiff): void; onPaste?(event: ClipboardEvent, model: EditorModel): boolean; } @@ -130,7 +130,7 @@ export default class BasicMessageEditor extends React.Component private isIMEComposing = false; private hasTextSelected = false; - private _isCaretAtEnd: boolean; + private _isCaretAtEnd = false; private lastCaret: DocumentOffset; private lastSelection: ReturnType | null = null; @@ -230,7 +230,7 @@ export default class BasicMessageEditor extends React.Component } } - private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff): void => { + private updateEditorState = (selection?: Caret, inputType?: string, diff?: IDiff): void => { if (!this.editorRef.current) return; renderModel(this.editorRef.current, this.props.model); if (selection) { diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index bfd9906e7e..dc95b5044e 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -39,6 +39,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher"; import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds"; import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import { EmojiButton } from "./EmojiButton"; +import { filterBoolean } from "../../../utils/arrays"; import { useSettingValue } from "../../../hooks/useSettings"; import { ButtonEvent } from "../elements/AccessibleButton"; @@ -118,8 +119,8 @@ const MessageComposerButtons: React.FC = (props: IProps) => { ]; } - mainButtons = mainButtons.filter((x: ReactElement) => x); - moreButtons = moreButtons.filter((x: ReactElement) => x); + mainButtons = filterBoolean(mainButtons); + moreButtons = filterBoolean(moreButtons); const moreOptionsClasses = classNames({ mx_MessageComposer_button: true, diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index d6d59287a4..25c47a456b 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -313,7 +313,7 @@ export default class RoomSublist extends React.Component { private onResize = ( e: MouseEvent | TouchEvent, travelDirection: Direction, - refToElement: HTMLDivElement, + refToElement: HTMLElement, delta: ResizeDelta, ): void => { const newHeight = this.heightAtStart + delta.height; @@ -329,7 +329,7 @@ export default class RoomSublist extends React.Component { private onResizeStop = ( e: MouseEvent | TouchEvent, travelDirection: Direction, - refToElement: HTMLDivElement, + refToElement: HTMLElement, delta: ResizeDelta, ): void => { const newHeight = this.heightAtStart + delta.height; diff --git a/src/components/views/rooms/SearchBar.tsx b/src/components/views/rooms/SearchBar.tsx index a4dbfe60ce..d171ad0628 100644 --- a/src/components/views/rooms/SearchBar.tsx +++ b/src/components/views/rooms/SearchBar.tsx @@ -27,7 +27,7 @@ import SearchWarning, { WarningKind } from "../elements/SearchWarning"; interface IProps { onCancelClick: () => void; - onSearch: (query: string, scope: string) => void; + onSearch: (query: string, scope: SearchScope) => void; searchInProgress?: boolean; isRoomEncrypted?: boolean; } diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 934a998dd9..0789c41905 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -686,7 +686,7 @@ export class SendMessageComposer extends React.Component { + private onChange = (selection?: Caret, inputType?: string, diff?: IDiff): void => { // We call this in here rather than onKeyDown as that would trip it on global shortcuts e.g. Ctrl-k also if (!!diff) { this.prepareToEncrypt?.(); diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx index ef79b98d48..c08ee2381f 100644 --- a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx @@ -31,7 +31,7 @@ interface IKeyboardShortcutRowProps { } // Filter out the labs section if labs aren't enabled. -const visibleCategories = Object.entries(CATEGORIES).filter( +const visibleCategories = (Object.entries(CATEGORIES) as [CategoryName, ICategory][]).filter( ([categoryName]) => categoryName !== CategoryName.LABS || SdkConfig.get("show_labs_settings"), ); @@ -73,7 +73,7 @@ const KeyboardUserSettingsTab: React.FC = () => { return (
{_t("Keyboard")}
- {visibleCategories.map(([categoryName, category]: [CategoryName, ICategory]) => { + {visibleCategories.map(([categoryName, category]) => { return ; })}
diff --git a/src/editor/dom.ts b/src/editor/dom.ts index 7c3fd3072b..293c7b02d8 100644 --- a/src/editor/dom.ts +++ b/src/editor/dom.ts @@ -147,7 +147,7 @@ function getTextAndOffsetToNode( let foundNode = false; let text = ""; - function enterNodeCallback(node: HTMLElement): boolean { + function enterNodeCallback(node: Node): boolean { if (!foundNode) { if (node === selectionNode) { foundNode = true; @@ -157,7 +157,7 @@ function getTextAndOffsetToNode( // but for example while pasting in some browsers, they are still // converted to BRs, so also take these into account when they // are not the last element in the DIV. - if (node.tagName === "BR" && node.nextSibling) { + if (node instanceof HTMLElement && node.tagName === "BR" && node.nextSibling) { if (!foundNode) { offsetToNode += 1; } @@ -173,12 +173,16 @@ function getTextAndOffsetToNode( return true; } - function leaveNodeCallback(node: HTMLElement): void { + function leaveNodeCallback(node: Node): void { // if this is not the last DIV (which are only used as line containers atm) // we don't just check if there is a nextSibling because sometimes the caret ends up // after the last DIV and it creates a newline if you type then, // whereas you just want it to be appended to the current line - if (node.tagName === "DIV" && (node.nextSibling)?.tagName === "DIV") { + if ( + node instanceof HTMLElement && + node.tagName === "DIV" && + (node.nextSibling)?.tagName === "DIV" + ) { text += "\n"; if (!foundNode) { offsetToNode += 1; diff --git a/src/editor/history.ts b/src/editor/history.ts index 074c65cf10..00bbbddbac 100644 --- a/src/editor/history.ts +++ b/src/editor/history.ts @@ -21,7 +21,7 @@ import { Caret } from "./caret"; export interface IHistory { parts: SerializedPart[]; - caret: Caret; + caret?: Caret; } export const MAX_STEP_LENGTH = 10; @@ -31,7 +31,7 @@ export default class HistoryManager { private newlyTypedCharCount = 0; private currentIndex = -1; private changedSinceLastPush = false; - private lastCaret: Caret | null = null; + private lastCaret?: Caret; private nonWordBoundarySinceLastPush = false; private addedSinceLastPush = false; private removedSinceLastPush = false; @@ -41,7 +41,7 @@ export default class HistoryManager { this.newlyTypedCharCount = 0; this.currentIndex = -1; this.changedSinceLastPush = false; - this.lastCaret = null; + this.lastCaret = undefined; this.nonWordBoundarySinceLastPush = false; this.addedSinceLastPush = false; this.removedSinceLastPush = false; @@ -85,7 +85,7 @@ export default class HistoryManager { } } - private pushState(model: EditorModel, caret: Caret): void { + private pushState(model: EditorModel, caret?: Caret): void { // remove all steps after current step while (this.currentIndex < this.stack.length - 1) { this.stack.pop(); @@ -93,7 +93,7 @@ export default class HistoryManager { const parts = model.serializeParts(); this.stack.push({ parts, caret }); this.currentIndex = this.stack.length - 1; - this.lastCaret = null; + this.lastCaret = undefined; this.changedSinceLastPush = false; this.newlyTypedCharCount = 0; this.nonWordBoundarySinceLastPush = false; @@ -102,7 +102,7 @@ export default class HistoryManager { } // needs to persist parts and caret position - public tryPush(model: EditorModel, caret: Caret, inputType?: string, diff?: IDiff): boolean { + public tryPush(model: EditorModel, caret?: Caret, inputType?: string, diff?: IDiff): boolean { // ignore state restoration echos. // these respect the inputType values of the input event, // but are actually passed in from MessageEditor calling model.reset() diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts index a49c231723..9572862d00 100644 --- a/src/hooks/usePublicRoomDirectory.ts +++ b/src/hooks/usePublicRoomDirectory.ts @@ -49,7 +49,7 @@ export const usePublicRoomDirectory = (): { publicRooms: IPublicRoomsChunkRoom[]; protocols: Protocols | null; config?: IPublicRoomDirectoryConfig | null; - setConfig(config: IPublicRoomDirectoryConfig): void; + setConfig(config: IPublicRoomDirectoryConfig | null): void; search(opts: IPublicRoomsOpts): Promise; } => { const [publicRooms, setPublicRooms] = useState([]); diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index c2acab5b00..2abecef074 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -28,6 +28,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; +import { HTTPError } from "matrix-js-sdk/src/http-api"; import PlatformPeg from "../PlatformPeg"; import { MatrixClientPeg } from "../MatrixClientPeg"; @@ -471,7 +472,7 @@ export default class EventIndex extends EventEmitter { checkpoint.direction, ); } catch (e) { - if (e.httpStatus === 403) { + if (e instanceof HTTPError && e.httpStatus === 403) { logger.log( "EventIndex: Removing checkpoint as we don't have ", "permissions to fetch messages from this room.", @@ -564,7 +565,7 @@ export default class EventIndex extends EventEmitter { return object; }); - let newCheckpoint; + let newCheckpoint: ICrawlerCheckpoint | null = null; // The token can be null for some reason. Don't create a checkpoint // in that case since adding it to the db will fail. diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index b0ec8a0fec..d370d5d2ff 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -45,7 +45,7 @@ export enum Phase { export class SetupEncryptionStore extends EventEmitter { private started?: boolean; - public phase: Phase; + public phase?: Phase; public verificationRequest: VerificationRequest | null = null; public backupInfo: IKeyBackupInfo | null = null; // ID of the key that the secrets we want are encrypted with diff --git a/src/theme.ts b/src/theme.ts index e85460e9ba..5fc7a6e50c 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -145,8 +145,8 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string { return ""; }) .join(", "); - const props = Object.keys(face).filter((prop: (typeof allowedFontFaceProps)[number]) => - allowedFontFaceProps.includes(prop), + const props = Object.keys(face).filter((prop) => + allowedFontFaceProps.includes(prop as (typeof allowedFontFaceProps)[number]), ) as Array<(typeof allowedFontFaceProps)[number]>; const body = props .map((prop) => { diff --git a/src/utils/IdentityServerUtils.ts b/src/utils/IdentityServerUtils.ts index 06a01b8a9d..96a0021e3b 100644 --- a/src/utils/IdentityServerUtils.ts +++ b/src/utils/IdentityServerUtils.ts @@ -16,6 +16,7 @@ limitations under the License. import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { logger } from "matrix-js-sdk/src/logger"; +import { HTTPError } from "matrix-js-sdk/src/http-api"; import SdkConfig from "../SdkConfig"; import { MatrixClientPeg } from "../MatrixClientPeg"; @@ -39,7 +40,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise; - private resolve: () => void; - private reject: (err: Error) => void; + private deferred!: IDeferred; public constructor(private timeout: number) { this.setNotStarted(); @@ -39,10 +39,8 @@ export default class Timer { private setNotStarted(): void { this.timerHandle = undefined; this.startTs = undefined; - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }).finally(() => { + this.deferred = defer(); + this.deferred.promise = this.deferred.promise.finally(() => { this.timerHandle = undefined; }); } @@ -51,7 +49,7 @@ export default class Timer { const now = Date.now(); const elapsed = now - this.startTs!; if (elapsed >= this.timeout) { - this.resolve(); + this.deferred.resolve(); this.setNotStarted(); } else { const delta = this.timeout - elapsed; @@ -108,7 +106,7 @@ export default class Timer { public abort(): Timer { if (this.isRunning()) { clearTimeout(this.timerHandle); - this.reject(new Error("Timer was aborted.")); + this.deferred.reject(new Error("Timer was aborted.")); this.setNotStarted(); } return this; @@ -120,7 +118,7 @@ export default class Timer { *@return {Promise} */ public finished(): Promise { - return this.promise; + return this.deferred.promise; } public isRunning(): boolean { diff --git a/src/utils/beacon/useOwnLiveBeacons.ts b/src/utils/beacon/useOwnLiveBeacons.ts index 989d12c4fd..f20e446aec 100644 --- a/src/utils/beacon/useOwnLiveBeacons.ts +++ b/src/utils/beacon/useOwnLiveBeacons.ts @@ -65,7 +65,7 @@ export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeacon // select the beacon with latest expiry to display expiry time const beacon = liveBeaconIds - .map((beaconId) => OwnBeaconStore.instance.getBeaconById(beaconId)) + .map((beaconId) => OwnBeaconStore.instance.getBeaconById(beaconId)!) .sort(sortBeaconsByLatestExpiry) .shift(); diff --git a/test/editor/mock.ts b/test/editor/mock.ts index d1fcc45e96..a7e968b309 100644 --- a/test/editor/mock.ts +++ b/test/editor/mock.ts @@ -82,12 +82,12 @@ export function createPartCreator(completions: PillPart[] = []) { } export function createRenderer() { - const render = (c: Caret) => { + const render = (c?: Caret) => { render.caret = c; render.count += 1; }; render.count = 0; - render.caret = null as unknown as Caret; + render.caret = null as unknown as Caret | undefined; return render; } diff --git a/test/editor/position-test.ts b/test/editor/position-test.ts index ccaa66e868..afb8bf53f5 100644 --- a/test/editor/position-test.ts +++ b/test/editor/position-test.ts @@ -15,18 +15,7 @@ limitations under the License. */ import EditorModel from "../../src/editor/model"; -import { createPartCreator } from "./mock"; -import { Caret } from "../../src/editor/caret"; - -function createRenderer() { - const render = (c: Caret) => { - render.caret = c; - render.count += 1; - }; - render.count = 0; - render.caret = null; - return render; -} +import { createPartCreator, createRenderer } from "./mock"; describe("editor/position", function () { it("move first position backward in empty model", function () { diff --git a/test/stores/SetupEncryptionStore-test.ts b/test/stores/SetupEncryptionStore-test.ts index 8c25861438..a1df872a69 100644 --- a/test/stores/SetupEncryptionStore-test.ts +++ b/test/stores/SetupEncryptionStore-test.ts @@ -48,8 +48,8 @@ describe("SetupEncryptionStore", () => { client.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => { await opts?.authUploadDeviceSigningKeys?.(makeRequest); }); - mocked(accessSecretStorage).mockImplementation(async (func: () => Promise) => { - await func(); + mocked(accessSecretStorage).mockImplementation(async (func?: () => Promise) => { + await func!(); }); await setupEncryptionStore.resetConfirm();