diff --git a/src/@types/common.ts b/src/@types/common.ts index e6e69ab1ec..4aeea8a643 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -76,3 +76,5 @@ interface DeepReadonlyArray extends ReadonlyArray> {} type DeepReadonlyObject = { readonly [P in keyof T]: DeepReadonly; }; + +export type AtLeastOne }> = Partial & U[keyof U]; diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 71891a3e13..bf0d09db29 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -272,7 +272,8 @@ export default class LegacyCallHandler extends EventEmitter { return localNotificationsAreSilenced(cli); } - public silenceCall(callId: string): void { + public silenceCall(callId?: string): void { + if (!callId) return; this.silencedCalls.add(callId); this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls); @@ -281,8 +282,8 @@ export default class LegacyCallHandler extends EventEmitter { this.pause(AudioID.Ring); } - public unSilenceCall(callId: string): void { - if (this.isForcedSilent()) return; + public unSilenceCall(callId?: string): void { + if (!callId || this.isForcedSilent()) return; this.silencedCalls.delete(callId); this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls); this.play(AudioID.Ring); @@ -1182,8 +1183,9 @@ export default class LegacyCallHandler extends EventEmitter { // Prevent double clicking the call button const widget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type)); if (widget) { + const room = client.getRoom(roomId); // If there already is a Jitsi widget, pin it - WidgetLayoutStore.instance.moveToContainer(client.getRoom(roomId), widget, Container.Top); + if (room) WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top); return; } diff --git a/src/Modal.tsx b/src/Modal.tsx index e8c514b801..d78758685b 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -30,7 +30,7 @@ const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; // Type which accepts a React Component which looks like a Modal (accepts an onFinished prop) export type ComponentType = React.ComponentType<{ - onFinished?(...args: any): void; + onFinished(...args: any): void; }>; // Generic type which returns the props of the Modal component with the onFinished being optional. @@ -135,7 +135,7 @@ export class ModalManager extends TypedEventEmitter( - Element: React.ComponentType, + Element: C, props?: ComponentProps, className?: string, ): IHandle { @@ -157,7 +157,7 @@ export class ModalManager extends TypedEventEmitter( - prom: Promise, + prom: Promise, props?: ComponentProps, className?: string, options?: IOptions, @@ -301,7 +301,7 @@ export class ModalManager extends TypedEventEmitter( - prom: Promise, + prom: Promise, props?: ComponentProps, className?: string, ): IHandle { diff --git a/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.tsx b/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.tsx index 7363f192a2..b5983b66c0 100644 --- a/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.tsx +++ b/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.tsx @@ -15,11 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentType } from "react"; +import React from "react"; import dis from "../../../../dispatcher/dispatcher"; import { _t } from "../../../../languageHandler"; -import Modal from "../../../../Modal"; +import Modal, { ComponentType } from "../../../../Modal"; import { Action } from "../../../../dispatcher/actions"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; @@ -37,7 +37,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent { this.props.onFinished(); Modal.createDialogAsync( - import("./CreateKeyBackupDialog") as unknown as Promise>, + import("./CreateKeyBackupDialog") as unknown as Promise, undefined, undefined, /* priority = */ false, diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index b3c8fc26fd..e1427c8a7b 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -62,7 +62,7 @@ export interface InteractiveAuthProps { continueText?: string; continueKind?: string; // callback - makeRequest(auth?: IAuthData): Promise>; + makeRequest(auth: IAuthDict | null): Promise>; // callback called when the auth process has finished, // successfully or unsuccessfully. // @param {boolean} status True if the operation requiring @@ -200,7 +200,7 @@ export default class InteractiveAuthComponent extends React.Component => { + private requestCallback = (auth: IAuthDict | null, background: boolean): Promise> => { // This wrapper just exists because the js-sdk passes a second // 'busy' param for backwards compat. This throws the tests off // so discard it here. diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts index d28224729a..0fcb91992f 100644 --- a/src/components/structures/LegacyCallEventGrouper.ts +++ b/src/components/structures/LegacyCallEventGrouper.ts @@ -152,15 +152,21 @@ export default class LegacyCallEventGrouper extends EventEmitter { }; public answerCall = (): void => { - LegacyCallHandler.instance.answerCall(this.roomId); + const roomId = this.roomId; + if (!roomId) return; + LegacyCallHandler.instance.answerCall(roomId); }; public rejectCall = (): void => { - LegacyCallHandler.instance.hangupOrReject(this.roomId, true); + const roomId = this.roomId; + if (!roomId) return; + LegacyCallHandler.instance.hangupOrReject(roomId, true); }; public callBack = (): void => { - LegacyCallHandler.instance.placeCall(this.roomId, this.isVoice ? CallType.Voice : CallType.Video); + const roomId = this.roomId; + if (!roomId) return; + LegacyCallHandler.instance.placeCall(roomId, this.isVoice ? CallType.Voice : CallType.Video); }; public toggleSilenced = (): void => { @@ -191,9 +197,10 @@ export default class LegacyCallEventGrouper extends EventEmitter { }; private setCall = (): void => { - if (this.call) return; + const callId = this.callId; + if (!callId || this.call) return; - this.call = LegacyCallHandler.instance.getCallById(this.callId); + this.call = LegacyCallHandler.instance.getCallById(callId); this.setCallListeners(); this.setState(); }; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index afc35508ac..fb3cb8e291 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -99,7 +99,7 @@ interface IProps { currentRoomId: string; collapseLhs: boolean; config: ConfigOptions; - currentUserId?: string; + currentUserId: string; justRegistered?: boolean; roomJustCreatedOpts?: IOpts; forceTimeline?: boolean; // see props on MatrixChat @@ -360,7 +360,7 @@ class LoggedInView extends React.Component { } } - if (pinnedEventTs && this.state.usageLimitEventTs > pinnedEventTs) { + if (pinnedEventTs && this.state.usageLimitEventTs && this.state.usageLimitEventTs > pinnedEventTs) { // We've processed a newer event than this one, so ignore it. return; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 07590446a3..d614a5edf3 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -422,7 +422,9 @@ export default class MatrixChat extends React.PureComponent { public componentDidUpdate(prevProps: IProps, prevState: IState): void { if (this.shouldTrackPageChange(prevState, this.state)) { const durationMs = this.stopPageChangeTimer(); - PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); + if (durationMs != null) { + PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); + } } if (this.focusComposer) { dis.fire(Action.FocusSendMessageComposer); @@ -935,7 +937,7 @@ export default class MatrixChat extends React.PureComponent { await this.firstSyncPromise.promise; } - let presentedId = roomInfo.room_alias || roomInfo.room_id; + let presentedId = roomInfo.room_alias || roomInfo.room_id!; const room = MatrixClientPeg.get().getRoom(roomInfo.room_id); if (room) { // Not all timeline events are decrypted ahead of time anymore diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 3d48c925fe..56d0db128b 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -308,7 +308,11 @@ export default class MessagePanel extends React.Component { this.calculateRoomMembersCount(); } - if (prevProps.readMarkerVisible && this.props.readMarkerEventId !== prevProps.readMarkerEventId) { + if ( + prevProps.readMarkerVisible && + prevProps.readMarkerEventId && + this.props.readMarkerEventId !== prevProps.readMarkerEventId + ) { const ghostReadMarkers = this.state.ghostReadMarkers; ghostReadMarkers.push(prevProps.readMarkerEventId); this.setState({ @@ -906,7 +910,7 @@ export default class MessagePanel extends React.Component { if (receiptsByUserId.get(userId)) { continue; } - const { lastShownEventId, receipt } = this.readReceiptsByUserId.get(userId); + const { lastShownEventId, receipt } = this.readReceiptsByUserId.get(userId)!; const existingReceipts = receiptsByEvent.get(lastShownEventId) || []; receiptsByEvent.set(lastShownEventId, existingReceipts.concat(receipt)); receiptsByUserId.set(userId, { lastShownEventId, receipt }); diff --git a/src/components/structures/PipContainer.tsx b/src/components/structures/PipContainer.tsx index 8ef9b1b816..6d761f9c79 100644 --- a/src/components/structures/PipContainer.tsx +++ b/src/components/structures/PipContainer.tsx @@ -239,7 +239,7 @@ class PipContainerInner extends React.Component { let notDocked = false; // Sanity check the room - the widget may have been destroyed between render cycles, and // thus no room is associated anymore. - if (persistentWidgetId && MatrixClientPeg.get().getRoom(persistentRoomId)) { + if (persistentWidgetId && persistentRoomId && MatrixClientPeg.get().getRoom(persistentRoomId)) { notDocked = !ActiveWidgetStore.instance.isDocked(persistentWidgetId, persistentRoomId); fromAnotherRoom = this.state.viewedRoomId !== persistentRoomId; } @@ -314,10 +314,10 @@ class PipContainerInner extends React.Component { )); } - if (this.state.showWidgetInPip) { + if (this.state.showWidgetInPip && this.state.persistentWidgetId) { pipContent.push(({ onStartMoving }) => ( { break; case RightPanelPhases.PinnedMessages: - if (SettingsStore.getValue("feature_pinning")) { + if (this.props.room && SettingsStore.getValue("feature_pinning")) { card = ( { private onSearch = throttle( (): void => { - this.props.onSearch(this.search.current?.value); + this.props.onSearch(this.search.current?.value ?? ""); }, 200, { trailing: true, leading: true }, @@ -94,11 +94,9 @@ export default class SearchBox extends React.Component { }; private clearSearch(source?: string): void { - this.search.current.value = ""; + if (this.search.current) this.search.current.value = ""; this.onChange(); - if (this.props.onCleared) { - this.props.onCleared(source); - } + this.props.onCleared?.(source); } public render(): React.ReactNode { diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 86d909748e..045ee6fccf 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { AuthType, createClient, IAuthData, IInputs, MatrixError } from "matrix-js-sdk/src/matrix"; +import { AuthType, createClient, IAuthDict, IAuthData, IInputs, MatrixError } from "matrix-js-sdk/src/matrix"; import React, { Fragment, ReactNode } from "react"; import { IRegisterRequestParams, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client"; import classNames from "classnames"; @@ -461,7 +461,7 @@ export default class Registration extends React.Component { }); }; - private makeRegisterRequest = (auth: IAuthData | null): Promise => { + private makeRegisterRequest = (auth: IAuthDict | null): Promise => { if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded"); const registerParams: IRegisterRequestParams = { diff --git a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx index e87fc16ef9..f4cf78681b 100644 --- a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx +++ b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx @@ -19,7 +19,7 @@ import React from "react"; import BaseDialog from "./BaseDialog"; import { _t } from "../../../languageHandler"; import DialogButtons from "../elements/DialogButtons"; -import Modal from "../../../Modal"; +import Modal, { ComponentProps } from "../../../Modal"; import SdkConfig from "../../../SdkConfig"; import { getPolicyUrl } from "../../../toasts/AnalyticsToast"; @@ -29,7 +29,7 @@ export enum ButtonClicked { } interface IProps { - onFinished?(buttonClicked?: ButtonClicked): void; + onFinished(buttonClicked?: ButtonClicked): void; analyticsOwner: string; privacyPolicyUrl?: string; primaryButton?: string; @@ -45,8 +45,8 @@ export const AnalyticsLearnMoreDialog: React.FC = ({ cancelButton, hasCancel, }) => { - const onPrimaryButtonClick = (): void => onFinished?.(ButtonClicked.Primary); - const onCancelButtonClick = (): void => onFinished?.(ButtonClicked.Cancel); + const onPrimaryButtonClick = (): void => onFinished(ButtonClicked.Primary); + const onCancelButtonClick = (): void => onFinished(ButtonClicked.Cancel); const privacyPolicyLink = privacyPolicyUrl ? ( {_t( @@ -114,7 +114,9 @@ export const AnalyticsLearnMoreDialog: React.FC = ({ ); }; -export const showDialog = (props: Omit): void => { +export const showDialog = ( + props: Omit, "cookiePolicyUrl" | "analyticsOwner">, +): void => { const privacyPolicyUrl = getPolicyUrl(); const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand"); Modal.createDialog( diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx index a262fd4f5e..00bb7cd7f6 100644 --- a/src/components/views/dialogs/BetaFeedbackDialog.tsx +++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx @@ -38,7 +38,7 @@ const BetaFeedbackDialog: React.FC = ({ featureId, onFinished }) => { return ( = (props: IProps) => { Modal.createDialog(BugReportDialog, {}); }; - const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url; - const hasFeedback = !!rageshakeUrl; + const hasFeedback = !!SdkConfig.get().bug_report_endpoint_url; const onFinished = (sendFeedback: boolean): void => { if (hasFeedback && sendFeedback) { - if (rageshakeUrl) { - const label = props.feature ? `${props.feature}-feedback` : "feedback"; - submitFeedback(rageshakeUrl, label, comment, canContact); - } + const label = props.feature ? `${props.feature}-feedback` : "feedback"; + submitFeedback(label, comment, canContact); Modal.createDialog(InfoDialog, { title: _t("Feedback sent"), @@ -65,8 +62,8 @@ const FeedbackDialog: React.FC = (props: IProps) => { props.onFinished(); }; - let feedbackSection; - if (rageshakeUrl) { + let feedbackSection: JSX.Element | undefined; + if (hasFeedback) { feedbackSection = (

{_t("Comment")}

@@ -93,8 +90,8 @@ const FeedbackDialog: React.FC = (props: IProps) => { ); } - let bugReports: JSX.Element | null = null; - if (rageshakeUrl) { + let bugReports: JSX.Element | undefined; + if (hasFeedback) { bugReports = (

{_t( diff --git a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx index 30e248a6d6..60acae926c 100644 --- a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx +++ b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx @@ -19,7 +19,6 @@ import React, { ReactNode, useState } from "react"; import QuestionDialog from "./QuestionDialog"; import { _t } from "../../../languageHandler"; import Field from "../elements/Field"; -import SdkConfig from "../../../SdkConfig"; import { submitFeedback } from "../../../rageshake/submit-rageshake"; import StyledCheckbox from "../elements/StyledCheckbox"; import Modal from "../../../Modal"; @@ -27,8 +26,8 @@ import InfoDialog from "./InfoDialog"; interface IProps { title: string; - subheading: string; - rageshakeLabel: string; + subheading?: string; + rageshakeLabel?: string; rageshakeData?: Record; children?: ReactNode; onFinished(sendFeedback?: boolean): void; @@ -48,7 +47,7 @@ const GenericFeatureFeedbackDialog: React.FC = ({ const sendFeedback = async (ok: boolean): Promise => { if (!ok) return onFinished(false); - submitFeedback(SdkConfig.get().bug_report_endpoint_url, rageshakeLabel, comment, canContact, rageshakeData); + submitFeedback(rageshakeLabel, comment, canContact, rageshakeData); onFinished(true); Modal.createDialog(InfoDialog, { diff --git a/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx index 8f1da8a253..2e71ff199e 100644 --- a/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx +++ b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx @@ -29,7 +29,7 @@ import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; interface IManualDeviceKeyVerificationDialogProps { userId: string; device: Device; - onFinished?(confirm?: boolean): void; + onFinished(confirm?: boolean): void; } export function ManualDeviceKeyVerificationDialog({ @@ -44,7 +44,7 @@ export function ManualDeviceKeyVerificationDialog({ if (confirm && mxClient) { mxClient.setDeviceVerified(userId, device.deviceId, true); } - onFinished?.(confirm); + onFinished(confirm); }, [mxClient, userId, device, onFinished], ); diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index cced3907bb..d4f36f8674 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -47,7 +47,7 @@ export class ModuleUiDialog extends ScrollableBaseModal { protected async submit(): Promise { try { - const model = await this.contentRef.current.trySubmit(); + const model = await this.contentRef.current!.trySubmit(); this.props.onFinished(true, model); } catch (e) { logger.error("Error during submission of module dialog:", e); diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index d7469b8694..aca963e422 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -120,7 +120,11 @@ export default class ServerPickerDialog extends React.PureComponent { - public constructor(props: IProps) { +export default class ShareDialog extends React.PureComponent, IState> { + public constructor(props: XOR) { super(props); let permalinkCreator: RoomPermalinkCreator | null = null; @@ -103,30 +112,25 @@ export default class ShareDialog extends React.PureComponent { }; private getUrl(): string { - let matrixToUrl; - if (this.props.target instanceof Room) { if (this.state.linkSpecificEvent) { const events = this.props.target.getLiveTimeline().getEvents(); - matrixToUrl = this.state.permalinkCreator!.forEvent(events[events.length - 1].getId()!); + return this.state.permalinkCreator!.forEvent(events[events.length - 1].getId()!); } else { - matrixToUrl = this.state.permalinkCreator!.forShareableRoom(); + return this.state.permalinkCreator!.forShareableRoom(); } } else if (this.props.target instanceof User || this.props.target instanceof RoomMember) { - matrixToUrl = makeUserPermalink(this.props.target.userId); - } else if (this.props.target instanceof MatrixEvent) { - if (this.state.linkSpecificEvent) { - matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId()!); - } else { - matrixToUrl = this.props.permalinkCreator.forShareableRoom(); - } + return makeUserPermalink(this.props.target.userId); + } else if (this.state.linkSpecificEvent) { + return this.props.permalinkCreator!.forEvent(this.props.target.getId()!); + } else { + return this.props.permalinkCreator!.forShareableRoom(); } - return matrixToUrl; } public render(): React.ReactNode { - let title; - let checkbox; + let title: string | undefined; + let checkbox: JSX.Element | undefined; if (this.props.target instanceof Room) { title = _t("Share Room"); diff --git a/src/components/views/dialogs/SlidingSyncOptionsDialog.tsx b/src/components/views/dialogs/SlidingSyncOptionsDialog.tsx index cf1bf354ce..9ef3e83ede 100644 --- a/src/components/views/dialogs/SlidingSyncOptionsDialog.tsx +++ b/src/components/views/dialogs/SlidingSyncOptionsDialog.tsx @@ -87,7 +87,7 @@ export const SlidingSyncOptionsDialog: React.FC<{ onFinished(enabled: boolean): const validProxy = withValidation({ async deriveData({ value }): Promise<{ error?: Error }> { try { - await proxyHealthCheck(value, MatrixClientPeg.get().baseUrl); + await proxyHealthCheck(value!, MatrixClientPeg.get().baseUrl); return {}; } catch (error) { return { error }; diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index a4ab599563..9cffc20241 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -42,7 +42,7 @@ const VALIDATION_THROTTLE_MS = 200; export type KeyParams = { passphrase?: string; recoveryKey?: string }; interface IProps { - keyInfo?: ISecretStorageKeyInfo; + keyInfo: ISecretStorageKeyInfo; checkPrivateKey: (k: KeyParams) => Promise; onFinished(result?: false | KeyParams): void; } diff --git a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx index bf4b170b25..31e85b1db4 100644 --- a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx +++ b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx @@ -136,6 +136,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent => { + if (!this.state.backupInfo) return; this.setState({ loading: true, restoreError: null, @@ -177,7 +178,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent => { - if (!this.state.recoveryKeyValid) return; + if (!this.state.recoveryKeyValid || !this.state.backupInfo) return; this.setState({ loading: true, @@ -228,6 +229,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent => { + if (!this.state.backupInfo) return; await MatrixClientPeg.get().restoreKeyBackupWithSecretStorage( this.state.backupInfo, undefined, diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index a6393b7c82..34b10dc095 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -748,7 +748,7 @@ export class UnwrappedEventTile extends React.Component } } - if (MatrixClientPeg.get().isRoomEncrypted(ev.getRoomId())) { + if (MatrixClientPeg.get().isRoomEncrypted(ev.getRoomId()!)) { // else if room is encrypted // and event is being encrypted or is not_sent (Unknown Devices/Network Error) if (ev.status === EventStatus.ENCRYPTING) { @@ -783,7 +783,7 @@ export class UnwrappedEventTile extends React.Component if (!this.props.showReactions || !this.props.getRelationsForEvent) { return null; } - const eventId = this.props.mxEvent.getId(); + const eventId = this.props.mxEvent.getId()!; return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction") ?? null; }; @@ -801,7 +801,7 @@ export class UnwrappedEventTile extends React.Component }; private onTimestampContextMenu = (ev: React.MouseEvent): void => { - this.showContextMenu(ev, this.props.permalinkCreator?.forEvent(this.props.mxEvent.getId())); + this.showContextMenu(ev, this.props.permalinkCreator?.forEvent(this.props.mxEvent.getId()!)); }; private showContextMenu(ev: React.MouseEvent, permalink?: string): void { @@ -974,7 +974,7 @@ export class UnwrappedEventTile extends React.Component let permalink = "#"; if (this.props.permalinkCreator) { - permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); + permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()!); } // we can't use local echoes as scroll tokens, because their event IDs change. diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 917e8d6ce3..233b1452f7 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -60,12 +60,12 @@ export default class ThirdPartyMemberInfo extends React.Component kickLevel : false, - senderName: sender ? sender.name : this.props.event.getSender(), + senderName: sender?.name ?? this.props.event.getSender(), }; } diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx index da67d6a919..1df87008c7 100644 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ b/src/components/views/settings/SecureBackupPanel.tsx @@ -123,7 +123,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { this.getUpdatedDiagnostics(); try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); + const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo!); if (this.unmounted) return; this.setState({ loading: false, @@ -285,7 +285,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { ); } - let backupSigStatuses: React.ReactNode = backupSigStatus.sigs.map((sig, i) => { + let backupSigStatuses: React.ReactNode = backupSigStatus?.sigs.map((sig, i) => { const deviceName = sig.device ? sig.device.getDisplayName() || sig.device.deviceId : null; const validity = (sub: string): JSX.Element => ( diff --git a/src/components/views/settings/devices/deleteDevices.tsx b/src/components/views/settings/devices/deleteDevices.tsx index c02c29f2cb..4b7af3119e 100644 --- a/src/components/views/settings/devices/deleteDevices.tsx +++ b/src/components/views/settings/devices/deleteDevices.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { IAuthData } from "matrix-js-sdk/src/interactive-auth"; +import { IAuthDict, IAuthData } from "matrix-js-sdk/src/interactive-auth"; import { _t } from "../../../../languageHandler"; import Modal from "../../../../Modal"; @@ -25,8 +25,8 @@ import InteractiveAuthDialog from "../../dialogs/InteractiveAuthDialog"; const makeDeleteRequest = (matrixClient: MatrixClient, deviceIds: string[]) => - async (auth?: IAuthData): Promise => { - return matrixClient.deleteMultipleDevices(deviceIds, auth); + async (auth: IAuthDict | null): Promise => { + return matrixClient.deleteMultipleDevices(deviceIds, auth ?? undefined); }; export const deleteDevicesWithInteractiveAuth = async ( @@ -38,7 +38,7 @@ export const deleteDevicesWithInteractiveAuth = async ( return; } try { - await makeDeleteRequest(matrixClient, deviceIds)(); + await makeDeleteRequest(matrixClient, deviceIds)(null); // no interactive auth needed onFinished(true, undefined); } catch (error) { diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 7305975be1..8162180b08 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -161,7 +161,7 @@ const SessionManagerTab: React.FC = () => { const shouldShowOtherSessions = otherSessionsCount > 0; const onVerifyCurrentDevice = (): void => { - Modal.createDialog(SetupEncryptionDialog as unknown as React.ComponentType, { onFinished: refreshDevices }); + Modal.createDialog(SetupEncryptionDialog, { onFinished: refreshDevices }); }; const onTriggerDeviceVerification = useCallback( diff --git a/src/dispatcher/payloads/ViewRoomPayload.ts b/src/dispatcher/payloads/ViewRoomPayload.ts index a2f999dddd..eacd94d37e 100644 --- a/src/dispatcher/payloads/ViewRoomPayload.ts +++ b/src/dispatcher/payloads/ViewRoomPayload.ts @@ -22,17 +22,12 @@ import { Action } from "../actions"; import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { IOpts } from "../../createRoom"; import { JoinRoomPayload } from "./JoinRoomPayload"; +import { AtLeastOne } from "../../@types/common"; /* eslint-disable camelcase */ -export interface ViewRoomPayload extends Pick { +interface BaseViewRoomPayload extends Pick { action: Action.ViewRoom; - // either or both of room_id or room_alias must be specified - // where possible, a room_id should be provided with a room_alias as it reduces - // the number of API calls required. - room_id?: string; - room_alias?: string; - event_id?: string; // the event to ensure is in view if any highlighted?: boolean; // whether to highlight `event_id` scroll_into_view?: boolean; // whether to scroll `event_id` into view @@ -57,4 +52,13 @@ export interface ViewRoomPayload extends Pick { metricsTrigger: ViewRoomEvent["trigger"]; metricsViaKeyboard?: ViewRoomEvent["viaKeyboard"]; } + +export type ViewRoomPayload = BaseViewRoomPayload & + AtLeastOne<{ + // either or both of room_id or room_alias must be specified + // where possible, a room_id should be provided with a room_alias as it reduces + // the number of API calls required. + room_id?: string; + room_alias?: string; + }>; /* eslint-enable camelcase */ diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 6f921469c7..456298ce11 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -146,7 +146,7 @@ export const options: Opts = { return { // @ts-ignore see https://linkify.js.org/docs/options.html click: function (e: MouseEvent) { - onUserClick(e, permalink.userId); + onUserClick(e, permalink.userId!); }, }; } else { diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 9027e2870f..bd40aefe7a 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -280,8 +280,7 @@ function uint8ToString(buf: Uint8Array): string { } export async function submitFeedback( - endpoint: string, - label: string, + label: string | undefined, comment: string, canContact = false, extraData: Record = {}, @@ -292,7 +291,7 @@ export async function submitFeedback( } catch (err) {} // PlatformPeg already logs this. const body = new FormData(); - body.append("label", label); + if (label) body.append("label", label); body.append("text", comment); body.append("can_contact", canContact ? "yes" : "no"); diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 641afe0e1b..68842d7dd6 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -215,7 +215,7 @@ export const SETTINGS: { [setting: string]: ISetting } = { ), feedbackLabel: "video-room-feedback", feedbackSubheading: _td( - "Thank you for trying the beta, " + "please go into as much detail as you can so we can improve it.", + "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", ), image: require("../../res/img/betas/video_rooms.png"), requiresRefresh: true, diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index caf85f28c8..e3e0183d40 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -308,7 +308,7 @@ export default class RightPanelStore extends ReadyWatchingStore { if (card.phase === RightPanelPhases.RoomMemberInfo && card.state) { // RightPanelPhases.RoomMemberInfo -> needs to be changed to RightPanelPhases.EncryptionPanel if there is a pending verification request const { member } = card.state; - const pendingRequest = pendingVerificationRequestForUser(member); + const pendingRequest = member ? pendingVerificationRequestForUser(member) : undefined; if (pendingRequest) { return { phase: RightPanelPhases.EncryptionPanel, diff --git a/src/stores/right-panel/RightPanelStoreIPanelState.ts b/src/stores/right-panel/RightPanelStoreIPanelState.ts index 826a767ee2..3599730e4f 100644 --- a/src/stores/right-panel/RightPanelStoreIPanelState.ts +++ b/src/stores/right-panel/RightPanelStoreIPanelState.ts @@ -71,8 +71,8 @@ interface IRightPanelForRoomStored { history: Array; } -export function convertToStorePanel(cacheRoom: IRightPanelForRoom): IRightPanelForRoomStored { - if (!cacheRoom) return cacheRoom; +export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | undefined { + if (!cacheRoom) return undefined; const storeHistory = [...cacheRoom.history].map((panelState) => convertCardToStore(panelState)); return { isOpen: cacheRoom.isOpen, history: storeHistory }; } diff --git a/src/utils/UserInteractiveAuth.ts b/src/utils/UserInteractiveAuth.ts index c953f0c9bd..2eed476c3c 100644 --- a/src/utils/UserInteractiveAuth.ts +++ b/src/utils/UserInteractiveAuth.ts @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IAuthData } from "matrix-js-sdk/src/interactive-auth"; +import { IAuthDict } from "matrix-js-sdk/src/interactive-auth"; import { UIAResponse } from "matrix-js-sdk/src/@types/uia"; import Modal from "../Modal"; import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "../components/views/dialogs/InteractiveAuthDialog"; -type FunctionWithUIA = (auth?: IAuthData, ...args: A[]) => Promise>; +type FunctionWithUIA = (auth?: IAuthDict | null, ...args: A[]) => Promise>; export function wrapRequestWithDialog( requestFunction: FunctionWithUIA, @@ -29,7 +29,7 @@ export function wrapRequestWithDialog( return async function (...args): Promise { return new Promise((resolve, reject) => { const boundFunction = requestFunction.bind(opts.matrixClient) as FunctionWithUIA; - boundFunction(undefined, ...args) + boundFunction(null, ...args) .then((res) => resolve(res as R)) .catch((error) => { if (error.httpStatus !== 401 || !error.data?.flows) { @@ -40,7 +40,7 @@ export function wrapRequestWithDialog( Modal.createDialog(InteractiveAuthDialog, { ...opts, authData: error.data, - makeRequest: (authData?: IAuthData) => boundFunction(authData, ...args), + makeRequest: (authData: IAuthDict | null) => boundFunction(authData, ...args), onFinished: (success, result) => { if (success) { resolve(result as R); diff --git a/src/utils/localRoom/isLocalRoom.ts b/src/utils/localRoom/isLocalRoom.ts index dffebd9777..c38ffa08e9 100644 --- a/src/utils/localRoom/isLocalRoom.ts +++ b/src/utils/localRoom/isLocalRoom.ts @@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/matrix"; import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom"; -export function isLocalRoom(roomOrID?: Room | string): boolean { +export function isLocalRoom(roomOrID?: Room | string | null): boolean { if (typeof roomOrID === "string") { return roomOrID.startsWith(LOCAL_ROOM_ID_PREFIX); } diff --git a/test/components/structures/LoggedInView-test.tsx b/test/components/structures/LoggedInView-test.tsx index 5e389d65de..6c9a3abb65 100644 --- a/test/components/structures/LoggedInView-test.tsx +++ b/test/components/structures/LoggedInView-test.tsx @@ -54,6 +54,7 @@ describe("", () => { element_call: {}, }, currentRoomId: "", + currentUserId: "@bob:server", }; const getComponent = (props = {}): RenderResult => diff --git a/test/components/structures/RoomSearchView-test.tsx b/test/components/structures/RoomSearchView-test.tsx index e2a8360d46..199d1eecc5 100644 --- a/test/components/structures/RoomSearchView-test.tsx +++ b/test/components/structures/RoomSearchView-test.tsx @@ -529,25 +529,25 @@ describe("", () => { ); const event1 = await screen.findByText("Room 1"); - expect(event1.closest(".mx_EventTile_line").querySelector("a")).toHaveAttribute( + expect(event1.closest(".mx_EventTile_line")!.querySelector("a")).toHaveAttribute( "href", `https://matrix.to/#/${room.roomId}/$2`, ); const event2 = await screen.findByText("Room 2"); - expect(event2.closest(".mx_EventTile_line").querySelector("a")).toHaveAttribute( + expect(event2.closest(".mx_EventTile_line")!.querySelector("a")).toHaveAttribute( "href", `https://matrix.to/#/${room2.roomId}/$22`, ); const event2Message2 = await screen.findByText("Room 2 message 2"); - expect(event2Message2.closest(".mx_EventTile_line").querySelector("a")).toHaveAttribute( + expect(event2Message2.closest(".mx_EventTile_line")!.querySelector("a")).toHaveAttribute( "href", `https://matrix.to/#/${room2.roomId}/$23`, ); const event3 = await screen.findByText("Room 3"); - expect(event3.closest(".mx_EventTile_line").querySelector("a")).toHaveAttribute( + expect(event3.closest(".mx_EventTile_line")!.querySelector("a")).toHaveAttribute( "href", `https://matrix.to/#/${room3.roomId}/$32`, ); diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 7d66f39dfb..22bcde2557 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -30,6 +30,7 @@ describe("AccessSecretStorageDialog", () => { let mockClient: Mocked; const defaultProps: ComponentProps = { + keyInfo: {} as any, onFinished: jest.fn(), checkPrivateKey: jest.fn(), };