diff --git a/src/components/views/messages/DownloadActionButton.tsx b/src/components/views/messages/DownloadActionButton.tsx index 53cdf74b32..d1d416d7e3 100644 --- a/src/components/views/messages/DownloadActionButton.tsx +++ b/src/components/views/messages/DownloadActionButton.tsx @@ -63,17 +63,17 @@ export default class DownloadActionButton extends React.PureComponent { + private async doDownload(blob: Blob): Promise { await this.downloader.download({ - blob: this.state.blob, + blob, name: this.props.mediaEventHelperGet().fileName, }); this.setState({ loading: false }); diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx index 312e694786..ca8a8599fd 100644 --- a/src/components/views/messages/EditHistoryMessage.tsx +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -79,7 +79,7 @@ export default class EditHistoryMessage extends React.PureComponent { - await cli.redactEvent(event.getRoomId(), event.getId()); + await cli.redactEvent(event.getRoomId()!, event.getId()); }, }, "mx_Dialog_confirmredact", diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 082ec4b0bf..86736d4e48 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -76,7 +76,7 @@ export default class MAudioBody extends React.PureComponent this.setState({ playback }); if (isVoiceMessage(this.props.mxEvent)) { - PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()).unsortedEnqueue(this.props.mxEvent, playback); + PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()!).unsortedEnqueue(this.props.mxEvent, playback); } // Note: the components later on will handle preparing the Playback class for us. diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index a3d680cb07..25327df74e 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -22,6 +22,7 @@ import { MatrixEventEvent, MatrixClient, RelationType, + IRedactOpts, } from "matrix-js-sdk/src/matrix"; import { BeaconLocationState } from "matrix-js-sdk/src/content-helpers"; import { randomString } from "matrix-js-sdk/src/randomstring"; @@ -107,15 +108,15 @@ const useHandleBeaconRedaction = ( const onBeforeBeaconInfoRedaction = useCallback( (_event: MatrixEvent, redactionEvent: MatrixEvent) => { const relations = getRelationsForEvent - ? getRelationsForEvent(event.getId(), RelationType.Reference, M_BEACON.name) + ? getRelationsForEvent(event.getId()!, RelationType.Reference, M_BEACON.name) : undefined; relations?.getRelations()?.forEach((locationEvent) => { matrixClient.redactEvent( - locationEvent.getRoomId(), - locationEvent.getId(), + locationEvent.getRoomId()!, + locationEvent.getId()!, undefined, - redactionEvent.getContent(), + redactionEvent.getContent(), ); }); }, @@ -132,7 +133,7 @@ const useHandleBeaconRedaction = ( const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelationsForEvent }, ref) => { const { beacon, isLive, latestLocationState, waitingToStart } = useBeaconState(mxEvent); - const mapId = useUniqueId(mxEvent.getId()); + const mapId = useUniqueId(mxEvent.getId()!); const matrixClient = useContext(MatrixClientContext); const [error, setError] = useState(); @@ -159,7 +160,7 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati Modal.createDialog( BeaconViewDialog, { - roomId: mxEvent.getRoomId(), + roomId: mxEvent.getRoomId()!, matrixClient, initialFocusedBeacon: beacon, }, @@ -170,11 +171,11 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati }; let map: JSX.Element; - if (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError) { + if (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError && latestLocationState?.uri) { map = ( = React.forwardRef(({ mxEvent, getRelati diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index df97bdad83..ae7a8d881d 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -78,7 +78,7 @@ cacheDownloadIcon(); * @param {HTMLElement} element The element to get the current style of. * @return {string} The CSS style encoded as a string. */ -export function computedStyle(element: HTMLElement): string { +export function computedStyle(element: HTMLElement | null): string { if (!element) { return ""; } @@ -142,6 +142,7 @@ export default class MFileBody extends React.Component { } private downloadFile(fileName: string, text: string): void { + if (!this.state.decryptedBlob) return; this.fileDownloader.download({ blob: this.state.decryptedBlob, name: fileName, diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index f556b59cf7..ad99aa142e 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -104,9 +104,10 @@ export default class MImageBody extends React.Component { const content = this.props.mxEvent.getContent(); const httpUrl = this.state.contentUrl; + if (!httpUrl) return; const params: Omit, "onFinished"> = { src: httpUrl, - name: content.body?.length > 0 ? content.body : _t("Attachment"), + name: content.body && content.body.length > 0 ? content.body : _t("Attachment"), mxEvent: this.props.mxEvent, permalinkCreator: this.props.permalinkCreator, }; @@ -135,7 +136,12 @@ export default class MImageBody extends React.Component { protected onImageEnter = (e: React.MouseEvent): void => { this.setState({ hover: true }); - if (!this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) { + if ( + !this.state.contentUrl || + !this.state.showImage || + !this.state.isAnimated || + SettingsStore.getValue("autoplayGifs") + ) { return; } const imgElement = e.currentTarget; @@ -145,11 +151,12 @@ export default class MImageBody extends React.Component { protected onImageLeave = (e: React.MouseEvent): void => { this.setState({ hover: false }); - if (!this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) { + const url = this.state.thumbUrl ?? this.state.contentUrl; + if (!url || !this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) { return; } const imgElement = e.currentTarget; - imgElement.src = this.state.thumbUrl ?? this.state.contentUrl; + imgElement.src = url; }; private clearError = (): void => { @@ -285,7 +292,7 @@ export default class MImageBody extends React.Component { img.onerror = reject; }); img.crossOrigin = "Anonymous"; // CORS allow canvas access - img.src = contentUrl; + img.src = contentUrl ?? ""; try { await loadPromise; @@ -379,7 +386,7 @@ export default class MImageBody extends React.Component { protected messageContent( contentUrl: string, - thumbUrl: string, + thumbUrl: string | null, content: IMediaEventContent, forcedHeight?: number, ): JSX.Element { @@ -579,7 +586,7 @@ export default class MImageBody extends React.Component { } let contentUrl = this.state.contentUrl; - let thumbUrl: string | undefined; + let thumbUrl: string | null; if (this.props.forExport) { contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url; thumbUrl = contentUrl; @@ -589,7 +596,7 @@ export default class MImageBody extends React.Component { thumbUrl = this.state.thumbUrl ?? this.state.contentUrl; } - const thumbnail = this.messageContent(contentUrl, thumbUrl, content); + const thumbnail = contentUrl ? this.messageContent(contentUrl, thumbUrl, content) : undefined; const fileBody = this.getFileBody(); return ( diff --git a/src/components/views/messages/MImageReplyBody.tsx b/src/components/views/messages/MImageReplyBody.tsx index 13a07c162a..68a39d23d6 100644 --- a/src/components/views/messages/MImageReplyBody.tsx +++ b/src/components/views/messages/MImageReplyBody.tsx @@ -36,7 +36,9 @@ export default class MImageReplyBody extends MImageBody { } const content = this.props.mxEvent.getContent(); - const thumbnail = this.messageContent(this.state.contentUrl, this.state.thumbUrl, content, FORCED_IMAGE_HEIGHT); + const thumbnail = this.state.contentUrl + ? this.messageContent(this.state.contentUrl, this.state.thumbUrl, content, FORCED_IMAGE_HEIGHT) + : undefined; return
{thumbnail}
; } diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 50b180beed..b299d96f6d 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -38,6 +38,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent { const prevUrl = this.props.mxEvent.getPrevContent()["url"]; const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender(); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + if (!room) return null; const widgetId = this.props.mxEvent.getStateKey(); const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId); diff --git a/src/components/views/messages/MKeyVerificationConclusion.tsx b/src/components/views/messages/MKeyVerificationConclusion.tsx index 24b925544e..fc354bd973 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.tsx +++ b/src/components/views/messages/MKeyVerificationConclusion.tsx @@ -114,16 +114,16 @@ export default class MKeyVerificationConclusion extends React.Component if (request.done) { title = _t("You verified %(name)s", { - name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()), + name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()!), }); } else if (request.cancelled) { const userId = request.cancellingUserId; if (userId === myUserId) { title = _t("You cancelled verifying %(name)s", { - name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()), + name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()!), }); - } else { - title = _t("%(name)s cancelled verifying", { name: getNameForEventRoom(userId, mxEvent.getRoomId()) }); + } else if (userId) { + title = _t("%(name)s cancelled verifying", { name: getNameForEventRoom(userId, mxEvent.getRoomId()!) }); } } @@ -135,7 +135,7 @@ export default class MKeyVerificationConclusion extends React.Component ); diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx index 8647a824ec..13bb4405ac 100644 --- a/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, User } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { VerificationRequestEvent } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; @@ -48,8 +48,11 @@ export default class MKeyVerificationRequest extends React.Component { } private openRequest = (): void => { + let member: User | undefined; const { verificationRequest } = this.props.mxEvent; - const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); + if (verificationRequest) { + member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId) ?? undefined; + } RightPanelStore.instance.setCards([ { phase: RightPanelPhases.RoomSummary }, { phase: RightPanelPhases.RoomMemberInfo, state: { member } }, @@ -90,14 +93,14 @@ export default class MKeyVerificationRequest extends React.Component { if (userId === myUserId) { return _t("You accepted"); } else { - return _t("%(name)s accepted", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); + return _t("%(name)s accepted", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) }); } } private cancelledLabel(userId: string): string { const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); - const { cancellationCode } = this.props.mxEvent.verificationRequest; + const cancellationCode = this.props.mxEvent.verificationRequest?.cancellationCode; const declined = cancellationCode === "m.user"; if (userId === myUserId) { if (declined) { @@ -107,9 +110,9 @@ export default class MKeyVerificationRequest extends React.Component { } } else { if (declined) { - return _t("%(name)s declined", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); + return _t("%(name)s declined", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) }); } else { - return _t("%(name)s cancelled", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); + return _t("%(name)s cancelled", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) }); } } } @@ -136,7 +139,7 @@ export default class MKeyVerificationRequest extends React.Component { ); } else if (request.cancelled) { - stateLabel = this.cancelledLabel(request.cancellingUserId); + stateLabel = this.cancelledLabel(request.cancellingUserId!); } else if (request.accepting) { stateLabel = _t("Accepting…"); } else if (request.declining) { @@ -146,9 +149,9 @@ export default class MKeyVerificationRequest extends React.Component { } if (!request.initiatedByMe) { - const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()); + const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()!); title = _t("%(name)s wants to verify", { name }); - subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()); + subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()!); if (request.canAccept) { stateNode = (
@@ -164,7 +167,7 @@ export default class MKeyVerificationRequest extends React.Component { } else { // request sent by us title = _t("You sent a verification request"); - subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId()); + subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId()!); } if (title) { diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index e65a8f67e6..21c3ee7ec1 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -118,16 +118,17 @@ export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?: } export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): void { + const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); if (pollAlreadyHasVotes(mxEvent, getRelationsForEvent)) { Modal.createDialog(ErrorDialog, { title: _t("Can't edit poll"), description: _t("Sorry, you can't edit a poll after votes have been cast."), }); - } else { + } else if (room) { Modal.createDialog( PollCreateDialog, { - room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), + room, threadId: mxEvent.getThread()?.id, editingMxEvent: mxEvent, }, diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index a7e60dfd5a..6be311e868 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -111,7 +111,7 @@ export default class MVideoBody extends React.PureComponent canvas.height = height; const pixels = decode(info[BLURHASH_FIELD], width, height); - const ctx = canvas.getContext("2d"); + const ctx = canvas.getContext("2d")!; const imgData = ctx.createImageData(width, height); imgData.data.set(pixels); ctx.putImageData(imgData, 0, 0); @@ -128,7 +128,7 @@ export default class MVideoBody extends React.PureComponent image.onload = () => { this.setState({ posterLoading: false }); }; - image.src = media.thumbnailHttp; + image.src = media.thumbnailHttp!; } } diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 7e2a8b8db9..65b7133b51 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -185,9 +185,9 @@ export default class MessageEvent extends React.Component implements IMe const allowRender = localStorage.getItem(key) === "true"; if (!allowRender) { - const userDomain = this.props.mxEvent.getSender().split(":").slice(1).join(":"); - const userBanned = Mjolnir.sharedInstance().isUserBanned(this.props.mxEvent.getSender()); - const serverBanned = Mjolnir.sharedInstance().isServerBanned(userDomain); + const userDomain = this.props.mxEvent.getSender()?.split(":").slice(1).join(":"); + const userBanned = Mjolnir.sharedInstance().isUserBanned(this.props.mxEvent.getSender()!); + const serverBanned = userDomain && Mjolnir.sharedInstance().isServerBanned(userDomain); if (userBanned || serverBanned) { BodyType = MjolnirBody; diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 6d7b0b2d17..bde1cba43c 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -34,8 +34,8 @@ const MAX_ITEMS_WHEN_LIMITED = 8; const ReactButton: React.FC = ({ mxEvent, reactions }) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); - let contextMenu; - if (menuDisplayed) { + let contextMenu: JSX.Element | undefined; + if (menuDisplayed && button.current) { const buttonRect = button.current.getBoundingClientRect(); contextMenu = ( @@ -73,7 +73,7 @@ interface IProps { } interface IState { - myReactions: MatrixEvent[]; + myReactions: MatrixEvent[] | null; showAll: boolean; } @@ -147,8 +147,9 @@ export default class ReactionsRow extends React.PureComponent { if (!reactions) { return null; } - const userId = this.context.room.client.getUserId(); - const myReactions = reactions.getAnnotationsBySender()[userId]; + const userId = this.context.room?.client.getUserId(); + if (!userId) return null; + const myReactions = reactions.getAnnotationsBySender()?.[userId]; if (!myReactions) { return null; } @@ -171,19 +172,17 @@ export default class ReactionsRow extends React.PureComponent { let items = reactions .getSortedAnnotationsByKey() - .map(([content, events]) => { + ?.map(([content, events]) => { const count = events.size; if (!count) { return null; } - const myReactionEvent = - myReactions && - myReactions.find((mxEvent) => { - if (mxEvent.isRedacted()) { - return false; - } - return mxEvent.getRelation().key === content; - }); + const myReactionEvent = myReactions?.find((mxEvent) => { + if (mxEvent.isRedacted()) { + return false; + } + return mxEvent.getRelation()?.key === content; + }); return ( { }) .filter((item) => !!item); - if (!items.length) return null; + if (!items?.length) return null; // Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items. // The "+ 1" ensure that the "show all" reveals something that takes up @@ -216,7 +215,7 @@ export default class ReactionsRow extends React.PureComponent { ); } - let addReactionButton: JSX.Element; + let addReactionButton: JSX.Element | undefined; if (this.context.canReact) { addReactionButton = ; } diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index 60bad283b7..284b713821 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -24,7 +24,6 @@ import dis from "../../../dispatcher/dispatcher"; import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip"; import AccessibleButton from "../elements/AccessibleButton"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; - interface IProps { // The event we're displaying reactions for mxEvent: MatrixEvent; @@ -57,9 +56,9 @@ export default class ReactionsRowButton extends React.PureComponent { const { mxEvent, myReactionEvent, content } = this.props; if (myReactionEvent) { - this.context.redactEvent(mxEvent.getRoomId(), myReactionEvent.getId()); + this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()); } else { - this.context.sendEvent(mxEvent.getRoomId(), "m.reaction", { + this.context.sendEvent(mxEvent.getRoomId()!, "m.reaction", { "m.relates_to": { rel_type: "m.annotation", event_id: mxEvent.getId(), @@ -110,8 +109,8 @@ export default class ReactionsRowButton extends React.PureComponent(({ mxEvent }, re } const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); - const fullDate = formatFullDate(new Date(unsigned.redacted_because.origin_server_ts), showTwelveHour); - const titleText = _t("Message deleted on %(date)s", { date: fullDate }); + const fullDate = unsigned.redacted_because + ? formatFullDate(new Date(unsigned.redacted_because.origin_server_ts), showTwelveHour) + : undefined; + const titleText = fullDate ? _t("Message deleted on %(date)s", { date: fullDate }) : undefined; return ( diff --git a/src/components/views/messages/RoomAvatarEvent.tsx b/src/components/views/messages/RoomAvatarEvent.tsx index 84fd4660bf..15e535210a 100644 --- a/src/components/views/messages/RoomAvatarEvent.tsx +++ b/src/components/views/messages/RoomAvatarEvent.tsx @@ -26,7 +26,6 @@ import AccessibleButton from "../elements/AccessibleButton"; import { mediaFromMxc } from "../../../customisations/Media"; import RoomAvatar from "../avatars/RoomAvatar"; import ImageView from "../elements/ImageView"; - interface IProps { /* the MatrixEvent to show */ mxEvent: MatrixEvent; @@ -37,6 +36,7 @@ export default class RoomAvatarEvent extends React.Component { const cli = MatrixClientPeg.get(); const ev = this.props.mxEvent; const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp; + if (!httpUrl) return; const room = cli.getRoom(this.props.mxEvent.getRoomId()); const text = _t("%(senderDisplayName)s changed the avatar for %(roomName)s", { @@ -48,7 +48,7 @@ export default class RoomAvatarEvent extends React.Component { src: httpUrl, name: text, }; - Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); + Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true); }; public render(): React.ReactNode { diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 05f6da046d..488c699aed 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -34,7 +34,6 @@ import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../util import { copyPlaintext } from "../../../utils/strings"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import UIStore from "../../../stores/UIStore"; -import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { Action } from "../../../dispatcher/actions"; import GenericTextContextMenu from "../context_menus/GenericTextContextMenu"; import Spoiler from "../elements/Spoiler"; @@ -109,7 +108,7 @@ export default class TextualBody extends React.Component { for (let i = 0; i < pres.length; i++) { // If there already is a div wrapping the codeblock we want to skip this. // This happens after the codeblock was edited. - if (pres[i].parentElement.className == "mx_EventTile_pre_container") continue; + if (pres[i].parentElement?.className == "mx_EventTile_pre_container") continue; // Add code element if it's missing since we depend on it if (pres[i].getElementsByTagName("code").length == 0) { this.addCodeElement(pres[i]); @@ -189,8 +188,8 @@ export default class TextualBody extends React.Component { if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom"; button.onclick = async (): Promise => { - const copyCode = button.parentElement.getElementsByTagName("code")[0]; - const successful = await copyPlaintext(copyCode.textContent); + const copyCode = button.parentElement?.getElementsByTagName("code")[0]; + const successful = copyCode?.textContent ? await copyPlaintext(copyCode.textContent) : false; const buttonRect = button.getBoundingClientRect(); const { close } = ContextMenu.createMenu(GenericTextContextMenu, { @@ -209,7 +208,7 @@ export default class TextualBody extends React.Component { div.className = "mx_EventTile_pre_container"; // Insert containing div in place of
 block
-        pre.parentNode.replaceChild(div, pre);
+        pre.parentNode?.replaceChild(div, pre);
         // Append 
 block and copy button to container
         div.appendChild(pre);
 
@@ -238,7 +237,7 @@ export default class TextualBody extends React.Component {
     }
 
     private highlightCode(code: HTMLElement): void {
-        if (code.textContent.length > MAX_HIGHLIGHT_LENGTH) {
+        if (code.textContent && code.textContent.length > MAX_HIGHLIGHT_LENGTH) {
             console.log(
                 "Code block is bigger than highlight limit (" +
                     code.textContent.length +
@@ -265,7 +264,7 @@ export default class TextualBody extends React.Component {
             // We don't use highlightElement here because we can't force language detection
             // off. It should use the one we've found in the CSS class but we'd rather pass
             // it in explicitly to make sure.
-            code.innerHTML = highlight.highlight(code.textContent, { language: advertisedLang }).value;
+            code.innerHTML = highlight.highlight(code.textContent ?? "", { language: advertisedLang }).value;
         } else if (
             SettingsStore.getValue("enableSyntaxHighlightLanguageDetection") &&
             code.parentElement instanceof HTMLPreElement
@@ -277,7 +276,7 @@ export default class TextualBody extends React.Component {
             // work on the DOM with highlightElement because that also adds CSS
             // classes to the pre/code element that we don't want (the CSS
             // conflicts with our own).
-            code.innerHTML = highlight.highlightAuto(code.textContent).value;
+            code.innerHTML = highlight.highlightAuto(code.textContent ?? "").value;
         }
     }
 
@@ -317,7 +316,7 @@ export default class TextualBody extends React.Component {
     private calculateUrlPreview(): void {
         //console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
 
-        if (this.props.showUrlPreview) {
+        if (this.props.showUrlPreview && this.contentRef.current) {
             // pass only the first child which is the event tile otherwise this recurses on edited events
             let links = this.findLinks([this.contentRef.current]);
             if (links.length) {
@@ -347,7 +346,7 @@ export default class TextualBody extends React.Component {
                 const spoiler = ;
 
                 ReactDOM.render(spoiler, spoilerContainer);
-                node.parentNode.replaceChild(spoilerContainer, node);
+                node.parentNode?.replaceChild(spoilerContainer, node);
 
                 node = spoilerContainer;
             }
@@ -395,12 +394,12 @@ export default class TextualBody extends React.Component {
         }
 
         const url = node.getAttribute("href");
-        const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
+        const host = url?.match(/^https?:\/\/(.*?)(\/|$)/)?.[1];
 
         // never preview permalinks (if anything we should give a smart
         // preview of the room/user they point to: nobody needs to be reminded
         // what the matrix.to site looks like).
-        if (isPermalinkHost(host)) return false;
+        if (!host || isPermalinkHost(host)) return false;
 
         if (node.textContent?.toLowerCase().trim().startsWith(host.toLowerCase())) {
             // it's a "foo.pl" style link
@@ -422,7 +421,7 @@ export default class TextualBody extends React.Component {
 
     private onEmoteSenderClick = (): void => {
         const mxEvent = this.props.mxEvent;
-        dis.dispatch({
+        dis.dispatch({
             action: Action.ComposerInsert,
             userId: mxEvent.getSender(),
             timelineRenderingType: this.context.timelineRenderingType,
@@ -482,10 +481,10 @@ export default class TextualBody extends React.Component {
 
         // Go fetch a scalar token
         const integrationManager = managers.getPrimaryManager();
-        const scalarClient = integrationManager.getScalarClient();
-        scalarClient.connect().then(() => {
+        const scalarClient = integrationManager?.getScalarClient();
+        scalarClient?.connect().then(() => {
             const completeUrl = scalarClient.getStarterLink(starterLink);
-            const integrationsUrl = integrationManager.uiUrl;
+            const integrationsUrl = integrationManager!.uiUrl;
             Modal.createDialog(QuestionDialog, {
                 title: _t("Add an Integration"),
                 description: (
@@ -508,7 +507,7 @@ export default class TextualBody extends React.Component {
                     const left = (window.screen.width - width) / 2;
                     const top = (window.screen.height - height) / 2;
                     const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
-                    const wnd = window.open(completeUrl, "_blank", features);
+                    const wnd = window.open(completeUrl, "_blank", features)!;
                     wnd.opener = null;
                 },
             });
diff --git a/src/settings/enums/ImageSize.ts b/src/settings/enums/ImageSize.ts
index 28f72cb8de..6c05497794 100644
--- a/src/settings/enums/ImageSize.ts
+++ b/src/settings/enums/ImageSize.ts
@@ -35,7 +35,7 @@ export enum ImageSize {
  * @param {number} maxHeight Overrides the default height limit
  * @returns {Dimensions} The suggested maximum dimensions for the image
  */
-export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
+export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Required {
     const aspectRatio = contentSize.w! / contentSize.h!;
     const portrait = aspectRatio < 1;
 
diff --git a/src/utils/FileDownloader.ts b/src/utils/FileDownloader.ts
index 3ed848e455..a2c89d0228 100644
--- a/src/utils/FileDownloader.ts
+++ b/src/utils/FileDownloader.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @typescript-eslint/naming-convention
+export type GetIframeFn = () => HTMLIFrameElement | null;
 
 export const DEFAULT_STYLES = {
     imgSrc: "",
@@ -75,7 +75,7 @@ export class FileDownloader {
      * @param iframeFn Function to get a pre-configured iframe. Set to null to have the downloader
      * use a generic, hidden, iframe.
      */
-    public constructor(private iframeFn?: getIframeFn) {}
+    public constructor(private iframeFn?: GetIframeFn) {}
 
     private get iframe(): HTMLIFrameElement {
         const iframe = this.iframeFn?.();