diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index a592c3b055..f8096ae561 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -652,9 +652,9 @@ export default class LegacyCallHandler extends EventEmitter { } private onCallStateChanged = (newState: CallState, oldState: CallState | null, call: MatrixCall): void => { - if (!this.matchesCallForThisRoom(call)) return; - const mappedRoomId = this.roomIdForCall(call); + if (!mappedRoomId || !this.matchesCallForThisRoom(call)) return; + this.setCallState(call, newState); dis.dispatch({ action: "call_state", diff --git a/src/audio/PlaybackClock.ts b/src/audio/PlaybackClock.ts index 1a6d1a2e5d..6775c09fca 100644 --- a/src/audio/PlaybackClock.ts +++ b/src/audio/PlaybackClock.ts @@ -60,7 +60,7 @@ export class PlaybackClock implements IDestroyable { private stopped = true; private lastCheck = 0; private observable = new SimpleObservable(); - private timerId: number; + private timerId?: number; private clipDuration = 0; private placeholderDuration = 0; diff --git a/src/audio/VoiceMessageRecording.ts b/src/audio/VoiceMessageRecording.ts index 7d5c491261..b46fd0da23 100644 --- a/src/audio/VoiceMessageRecording.ts +++ b/src/audio/VoiceMessageRecording.ts @@ -33,9 +33,9 @@ export interface IUpload { * This class can be used to record a single voice message. */ export class VoiceMessageRecording implements IDestroyable { - private lastUpload: IUpload; + private lastUpload?: IUpload; private buffer = new Uint8Array(0); // use this.audioBuffer to access - private playback: Playback; + private playback?: Playback; public constructor(private matrixClient: MatrixClient, private voiceRecording: VoiceRecording) { this.voiceRecording.onDataAvailable = this.onDataAvailable; diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index 033c5da475..2e1f5634fb 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -243,7 +243,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { return; } - const secondsLeft = TARGET_MAX_LENGTH - this.recorderSeconds; + const secondsLeft = TARGET_MAX_LENGTH - this.recorderSeconds!; if (secondsLeft < 0) { // go over to make sure we definitely capture that last frame // noinspection JSIgnoredPromiseFromCall - we aren't concerned with it overlapping @@ -259,7 +259,8 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { /** * {@link https://github.com/chris-rudmin/opus-recorder#instance-fields ref for recorderSeconds} */ - public get recorderSeconds(): number { + public get recorderSeconds(): number | undefined { + if (!this.recorder) return undefined; return this.recorder.encodedSamplePosition / 48000; } diff --git a/src/components/structures/IndicatorScrollbar.tsx b/src/components/structures/IndicatorScrollbar.tsx index 54d70add77..ce60ffc751 100644 --- a/src/components/structures/IndicatorScrollbar.tsx +++ b/src/components/structures/IndicatorScrollbar.tsx @@ -44,7 +44,7 @@ export default class IndicatorScrollbar e IState > { private autoHideScrollbar = createRef>(); - private scrollElement: HTMLDivElement; + private scrollElement?: HTMLDivElement; private likelyTrackpadUser: boolean | null = null; private checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser @@ -85,6 +85,7 @@ export default class IndicatorScrollbar e } private checkOverflow = (): void => { + if (!this.scrollElement) return; const hasTopOverflow = this.scrollElement.scrollTop > 0; const hasBottomOverflow = this.scrollElement.scrollHeight > this.scrollElement.scrollTop + this.scrollElement.clientHeight; diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts index 0fcb91992f..354116009b 100644 --- a/src/components/structures/LegacyCallEventGrouper.ts +++ b/src/components/structures/LegacyCallEventGrouper.ts @@ -73,7 +73,7 @@ export function buildLegacyCallEventGroupers( export default class LegacyCallEventGrouper extends EventEmitter { private events: Set = new Set(); private call: MatrixCall | null = null; - public state: CallState | CustomCallState; + public state?: CallState | CustomCallState; public constructor() { super(); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 141f61937d..85f73ee8e7 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -96,10 +96,10 @@ interface IProps { autoJoin?: boolean; threepidInvite?: IThreepidInvite; roomOobData?: IOOBData; - currentRoomId: string; + currentRoomId: string | null; collapseLhs: boolean; config: ConfigOptions; - currentUserId: string; + currentUserId: string | null; justRegistered?: boolean; roomJustCreatedOpts?: IOpts; forceTimeline?: boolean; // see props on MatrixChat @@ -131,10 +131,10 @@ class LoggedInView extends React.Component { protected readonly _roomView: React.RefObject; protected readonly _resizeContainer: React.RefObject; protected readonly resizeHandler: React.RefObject; - protected layoutWatcherRef: string; - protected compactLayoutWatcherRef: string; - protected backgroundImageWatcherRef: string; - protected resizer: Resizer; + protected layoutWatcherRef?: string; + protected compactLayoutWatcherRef?: string; + protected backgroundImageWatcherRef?: string; + protected resizer?: Resizer; public constructor(props: IProps) { super(props); @@ -200,10 +200,10 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener(ClientEvent.Sync, this.onSync); this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); OwnProfileStore.instance.off(UPDATE_EVENT, this.refreshBackgroundImage); - SettingsStore.unwatchSetting(this.layoutWatcherRef); - SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); - SettingsStore.unwatchSetting(this.backgroundImageWatcherRef); - this.resizer.detach(); + if (this.layoutWatcherRef) SettingsStore.unwatchSetting(this.layoutWatcherRef); + if (this.compactLayoutWatcherRef) SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); + if (this.backgroundImageWatcherRef) SettingsStore.unwatchSetting(this.backgroundImageWatcherRef); + this.resizer?.detach(); } private onCallState = (): void => { @@ -274,7 +274,7 @@ class LoggedInView extends React.Component { if (isNaN(lhsSize)) { lhsSize = 350; } - this.resizer.forHandleWithId("lp-resizer")?.resize(lhsSize); + this.resizer?.forHandleWithId("lp-resizer")?.resize(lhsSize); } private onAccountData = (event: MatrixEvent): void => { @@ -645,7 +645,11 @@ class LoggedInView extends React.Component { break; case PageTypes.UserView: - pageElement = ; + if (!!this.props.currentUserId) { + pageElement = ( + + ); + } break; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index cdecf9f61e..9932a102ef 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -231,7 +231,7 @@ export default class MatrixChat extends React.PureComponent { private focusComposer: boolean; private subTitleStatus: string; private prevWindowWidth: number; - private voiceBroadcastResumer: VoiceBroadcastResumer; + private voiceBroadcastResumer?: VoiceBroadcastResumer; private readonly loggedInView: React.RefObject; private readonly dispatcherRef: string; @@ -441,7 +441,7 @@ export default class MatrixChat extends React.PureComponent { window.removeEventListener("resize", this.onWindowResized); this.stores.accountPasswordStore.clearPassword(); - if (this.voiceBroadcastResumer) this.voiceBroadcastResumer.destroy(); + this.voiceBroadcastResumer?.destroy(); } private onWindowResized = (): void => { @@ -1935,7 +1935,7 @@ export default class MatrixChat extends React.PureComponent { private setPageSubtitle(subtitle = ""): void { if (this.state.currentRoomId) { const client = MatrixClientPeg.get(); - const room = client && client.getRoom(this.state.currentRoomId); + const room = client?.getRoom(this.state.currentRoomId); if (room) { subtitle = `${this.subTitleStatus} | ${room.name} ${subtitle}`; } diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 8fc232774e..61279cffb3 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -179,7 +179,7 @@ export default class ScrollPanel extends React.Component { }; private readonly itemlist = createRef(); private unmounted = false; - private scrollTimeout: Timer; + private scrollTimeout?: Timer; // Are we currently trying to backfill? private isFilling: boolean; // Is the current fill request caused by a props update? @@ -189,8 +189,8 @@ export default class ScrollPanel extends React.Component { // Is that next fill request scheduled because of a props update? private pendingFillDueToPropsUpdate: boolean; private scrollState: IScrollState; - private preventShrinkingState: IPreventShrinkingState | null; - private unfillDebouncer: number | null; + private preventShrinkingState: IPreventShrinkingState | null = null; + private unfillDebouncer: number | null = null; private bottomGrowth: number; private minListHeight: number; private heightUpdateInProgress: boolean; @@ -234,7 +234,7 @@ export default class ScrollPanel extends React.Component { // skip scroll events caused by resizing if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; debuglog("onScroll called past resize gate; scroll node top:", this.getScrollNode().scrollTop); - this.scrollTimeout.restart(); + this.scrollTimeout?.restart(); this.saveScrollState(); this.updatePreventShrinking(); this.props.onScroll?.(ev); @@ -725,7 +725,7 @@ export default class ScrollPanel extends React.Component { // need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content? private async updateHeight(): Promise { // wait until user has stopped scrolling - if (this.scrollTimeout.isRunning()) { + if (this.scrollTimeout?.isRunning()) { debuglog("updateHeight waiting for scrolling to end ... "); await this.scrollTimeout.finished(); debuglog("updateHeight actually running now"); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 5d2ba897ba..609bcdf3e4 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -88,9 +88,9 @@ export default class UserMenu extends React.Component { public static contextType = SDKContext; public context!: React.ContextType; - private dispatcherRef: string; - private themeWatcherRef: string; - private readonly dndWatcherRef: string; + private dispatcherRef?: string; + private themeWatcherRef?: string; + private readonly dndWatcherRef?: string; private buttonRef: React.RefObject = createRef(); public constructor(props: IProps, context: React.ContextType) { diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 045ee6fccf..d8fd848320 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -125,7 +125,7 @@ interface IState { export default class Registration extends React.Component { private readonly loginLogic: Login; // `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows - private latestServerConfig: ValidatedServerConfig; + private latestServerConfig?: ValidatedServerConfig; public constructor(props: IProps) { super(props); diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index a60aca3794..8431bfc01c 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -556,8 +556,8 @@ export class MsisdnAuthEntry extends React.Component { // Report to moderators through to the dedicated bot, // as configured in the room's state events. const dmRoomId = await ensureDMExists(client, this.moderation.moderationBotUserId); + if (!dmRoomId) { + throw new UserFriendlyError("Unable to create room with moderation bot"); + } + await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, { event_id: ev.getId(), room_id: ev.getRoomId(), diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 2c4a9745b5..55e49843b2 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -62,7 +62,7 @@ interface IState { } class RoomSettingsDialog extends React.Component { - private dispatcherRef: string; + private dispatcherRef?: string; public constructor(props: IProps) { super(props); diff --git a/src/components/views/dialogs/RoomUpgradeDialog.tsx b/src/components/views/dialogs/RoomUpgradeDialog.tsx index 9acf8b6ef8..7737f4fbc7 100644 --- a/src/components/views/dialogs/RoomUpgradeDialog.tsx +++ b/src/components/views/dialogs/RoomUpgradeDialog.tsx @@ -35,7 +35,7 @@ interface IState { } export default class RoomUpgradeDialog extends React.Component { - private targetVersion: string; + private targetVersion?: string; public state = { busy: true, @@ -53,7 +53,7 @@ export default class RoomUpgradeDialog extends React.Component { private onUpgradeClick = (): void => { this.setState({ busy: true }); - upgradeRoom(this.props.room, this.targetVersion, false, false) + upgradeRoom(this.props.room, this.targetVersion!, false, false) .then(() => { this.props.onFinished(true); }) @@ -69,7 +69,7 @@ export default class RoomUpgradeDialog extends React.Component { }; public render(): React.ReactNode { - let buttons; + let buttons: JSX.Element; if (this.state.busy) { buttons = ; } else { diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 8627e8b3d2..5a06db2da6 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -44,7 +44,7 @@ interface IState { export default class ServerPickerDialog extends React.PureComponent { private readonly defaultServer: ValidatedServerConfig; private readonly fieldRef = createRef(); - private validatedConf: ValidatedServerConfig; + private validatedConf?: ValidatedServerConfig; public constructor(props: IProps) { super(props); @@ -85,7 +85,7 @@ export default class ServerPickerDialog extends React.PureComponent({ deriveData: async ({ value }): Promise<{ error?: string }> => { - let hsUrl = value.trim(); // trim to account for random whitespace + let hsUrl = (value ?? "").trim(); // trim to account for random whitespace // if the URL has no protocol, try validate it as a serverName via well-known if (!hsUrl.includes("://")) { diff --git a/src/components/views/dialogs/SetEmailDialog.tsx b/src/components/views/dialogs/SetEmailDialog.tsx index b5b148ea20..bec380e66a 100644 --- a/src/components/views/dialogs/SetEmailDialog.tsx +++ b/src/components/views/dialogs/SetEmailDialog.tsx @@ -45,7 +45,7 @@ interface IState { * On success, `onFinished(true)` is called. */ export default class SetEmailDialog extends React.Component { - private addThreepid: AddThreepid; + private addThreepid?: AddThreepid; public constructor(props: IProps) { super(props); @@ -109,7 +109,7 @@ export default class SetEmailDialog extends React.Component { }; private verifyEmailAddress(): void { - this.addThreepid.checkEmailLinkClicked().then( + this.addThreepid?.checkEmailLinkClicked().then( () => { this.props.onFinished(true); }, diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index 5bc2678e70..2749836ee1 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -121,11 +121,11 @@ export default class AppTile extends React.Component { }; private contextMenuButton = createRef(); - private iframe: HTMLIFrameElement; // ref to the iframe (callback style) - private allowedWidgetsWatchRef: string; + private iframe?: HTMLIFrameElement; // ref to the iframe (callback style) + private allowedWidgetsWatchRef?: string; private persistKey: string; private sgWidget: StopGapWidget | null; - private dispatcherRef: string; + private dispatcherRef?: string; private unmounted: boolean; public constructor(props: IProps) { @@ -305,7 +305,7 @@ export default class AppTile extends React.Component { this.context.off(RoomEvent.MyMembership, this.onMyMembership); } - SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef); + if (this.allowedWidgetsWatchRef) SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef); OwnProfileStore.instance.removeListener(UPDATE_EVENT, this.onUserReady); } @@ -344,7 +344,7 @@ export default class AppTile extends React.Component { private startMessaging(): void { try { - this.sgWidget?.startMessaging(this.iframe); + this.sgWidget?.startMessaging(this.iframe!); } catch (e) { logger.error("Failed to start widget", e); } diff --git a/src/components/views/elements/DesktopCapturerSourcePicker.tsx b/src/components/views/elements/DesktopCapturerSourcePicker.tsx index 20bbc729ad..fd9e019ee6 100644 --- a/src/components/views/elements/DesktopCapturerSourcePicker.tsx +++ b/src/components/views/elements/DesktopCapturerSourcePicker.tsx @@ -88,7 +88,7 @@ export interface PickerIProps { type TabId = "screen" | "window"; export default class DesktopCapturerSourcePicker extends React.Component { - public interval: number; + public interval?: number; public constructor(props: PickerIProps) { super(props); diff --git a/src/components/views/elements/PersistedElement.tsx b/src/components/views/elements/PersistedElement.tsx index dd2c41b249..b1d53247db 100644 --- a/src/components/views/elements/PersistedElement.tsx +++ b/src/components/views/elements/PersistedElement.tsx @@ -75,8 +75,8 @@ interface IProps { export default class PersistedElement extends React.Component { private resizeObserver: ResizeObserver; private dispatcherRef: string; - private childContainer: HTMLDivElement; - private child: HTMLDivElement; + private childContainer?: HTMLDivElement; + private child?: HTMLDivElement; public constructor(props: IProps) { super(props); @@ -172,12 +172,12 @@ export default class PersistedElement extends React.Component { ReactDOM.render(content, getOrCreateContainer("mx_persistedElement_" + this.props.persistKey)); } - private updateChildVisibility(child: HTMLDivElement, visible: boolean): void { + private updateChildVisibility(child?: HTMLDivElement, visible = false): void { if (!child) return; child.style.display = visible ? "block" : "none"; } - private updateChildPosition(child: HTMLDivElement, parent: HTMLDivElement): void { + private updateChildPosition(child?: HTMLDivElement, parent?: HTMLDivElement): void { if (!child || !parent) return; const parentRect = parent.getBoundingClientRect(); diff --git a/src/components/views/messages/IMediaBody.ts b/src/components/views/messages/IMediaBody.ts index 27b5f24275..b578edbbef 100644 --- a/src/components/views/messages/IMediaBody.ts +++ b/src/components/views/messages/IMediaBody.ts @@ -17,5 +17,5 @@ limitations under the License. import { MediaEventHelper } from "../../../utils/MediaEventHelper"; export interface IMediaBody { - getMediaHelper(): MediaEventHelper; + getMediaHelper(): MediaEventHelper | undefined; } diff --git a/src/components/views/messages/LegacyCallEvent.tsx b/src/components/views/messages/LegacyCallEvent.tsx index a8ba902e61..6d7bc3db8f 100644 --- a/src/components/views/messages/LegacyCallEvent.tsx +++ b/src/components/views/messages/LegacyCallEvent.tsx @@ -48,7 +48,7 @@ interface IState { export default class LegacyCallEvent extends React.PureComponent { private wrapperElement = createRef(); - private resizeObserver: ResizeObserver; + private resizeObserver?: ResizeObserver; public constructor(props: IProps) { super(props); @@ -75,7 +75,7 @@ export default class LegacyCallEvent extends React.PureComponent this.props.callEventGrouper.removeListener(LegacyCallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); this.props.callEventGrouper.removeListener(LegacyCallEventGrouperEvent.LengthChanged, this.onLengthChanged); - this.resizeObserver.disconnect(); + this.resizeObserver?.disconnect(); } private onLengthChanged = (length: number): void => { diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 27ccdd4228..e7c0964d16 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -69,7 +69,7 @@ export default class MImageBody extends React.Component { private unmounted = true; private image = createRef(); private timeout?: number; - private sizeWatcher: string; + private sizeWatcher?: string; private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync]; public constructor(props: IBodyProps) { @@ -367,7 +367,7 @@ export default class MImageBody extends React.Component { this.unmounted = true; MatrixClientPeg.get().off(ClientEvent.Sync, this.reconnectedListener); this.clearBlurhashTimeout(); - SettingsStore.unwatchSetting(this.sizeWatcher); + if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher); if (this.state.isAnimated && this.state.thumbUrl) { URL.revokeObjectURL(this.state.thumbUrl); } diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 6be311e868..5c2cd78fa8 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -45,7 +45,7 @@ export default class MVideoBody extends React.PureComponent public context!: React.ContextType; private videoRef = React.createRef(); - private sizeWatcher: string; + private sizeWatcher?: string; public constructor(props: IBodyProps) { super(props); @@ -187,7 +187,7 @@ export default class MVideoBody extends React.PureComponent } public componentWillUnmount(): void { - SettingsStore.unwatchSetting(this.sizeWatcher); + if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher); } private videoOnPlay = async (): Promise => { diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 65b7133b51..4fd84e31f1 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -82,7 +82,7 @@ const baseEvTypes = new Map>>([ export default class MessageEvent extends React.Component implements IMediaBody, IOperableEventTile { private body: React.RefObject = createRef(); - private mediaHelper: MediaEventHelper; + private mediaHelper?: MediaEventHelper; private bodyTypes = new Map(baseBodyTypes.entries()); private evTypes = new Map>>(baseEvTypes.entries()); @@ -133,7 +133,7 @@ export default class MessageEvent extends React.Component implements IMe return (this.body.current as IOperableEventTile)?.getEventTileOps?.() || null; }; - public getMediaHelper(): MediaEventHelper { + public getMediaHelper(): MediaEventHelper | undefined { return this.mediaHelper; } diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index e869c9ee5b..90fb65a3c9 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -57,9 +57,9 @@ interface IState { export default class AppsDrawer extends React.Component { private unmounted = false; - private resizeContainer: HTMLDivElement; + private resizeContainer?: HTMLDivElement; private resizer: Resizer; - private dispatcherRef: string; + private dispatcherRef?: string; public static defaultProps: Partial = { showApps: true, }; @@ -113,11 +113,11 @@ export default class AppsDrawer extends React.Component { }; const collapseConfig = { onResizeStart: () => { - this.resizeContainer.classList.add("mx_AppsDrawer_resizing"); + this.resizeContainer?.classList.add("mx_AppsDrawer_resizing"); this.setState({ resizingHorizontal: true }); }, onResizeStop: () => { - this.resizeContainer.classList.remove("mx_AppsDrawer_resizing"); + this.resizeContainer?.classList.remove("mx_AppsDrawer_resizing"); WidgetLayoutStore.instance.setResizerDistributions( this.props.room, Container.Top, diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index b8f643eebb..667f93afa1 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -133,9 +133,9 @@ export default class Autocomplete extends React.PureComponent { }); } - private processQuery(query: string, selection: ISelectionRange): Promise { + private async processQuery(query: string, selection: ISelectionRange): Promise { return this.autocompleter - .getCompletions(query, selection, this.state.forceComplete, MAX_PROVIDER_MATCHES) + ?.getCompletions(query, selection, this.state.forceComplete, MAX_PROVIDER_MATCHES) .then((completions) => { // Only ever process the completions for the most recent query being processed if (query !== this.queryRequested) { diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index c1fdecdd47..9b581565cc 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -129,7 +129,7 @@ class EditMessageComposer extends React.Component(); private readonly dispatcherRef: string; private readonly replyToEvent?: MatrixEvent; - private model: EditorModel; + private model!: EditorModel; public constructor(props: IEditMessageComposerProps, context: React.ContextType) { super(props); diff --git a/src/components/views/rooms/MemberTile.tsx b/src/components/views/rooms/MemberTile.tsx index 7ff46c922f..f8f891a486 100644 --- a/src/components/views/rooms/MemberTile.tsx +++ b/src/components/views/rooms/MemberTile.tsx @@ -46,8 +46,8 @@ interface IState { } export default class MemberTile extends React.Component { - private userLastModifiedTime: number; - private memberLastModifiedTime: number; + private userLastModifiedTime?: number; + private memberLastModifiedTime?: number; public static defaultProps = { showPresence: true, diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 921c1e2467..06396de03c 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -443,7 +443,7 @@ const TAG_AESTHETICS: TagAestheticsMap = { export default class RoomList extends React.PureComponent { private dispatcherRef?: string; private treeRef = createRef(); - private favouriteMessageWatcher: string; + private favouriteMessageWatcher?: string; public static contextType = MatrixClientContext; public context!: React.ContextType; @@ -476,7 +476,7 @@ export default class RoomList extends React.PureComponent { public componentWillUnmount(): void { SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); - SettingsStore.unwatchSetting(this.favouriteMessageWatcher); + if (this.favouriteMessageWatcher) SettingsStore.unwatchSetting(this.favouriteMessageWatcher); if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); } diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx index 32fb134953..77b1603a23 100644 --- a/src/components/views/rooms/RoomPreviewBar.tsx +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -215,8 +215,10 @@ export default class RoomPreviewBar extends React.Component { if (!myMember) { return {}; } - const kickerMember = this.props.room?.currentState.getMember(myMember.events.member.getSender()); - const memberName = kickerMember?.name ?? myMember.events.member?.getSender(); + + const kickerUserId = myMember.events.member?.getSender(); + const kickerMember = kickerUserId ? this.props.room?.currentState.getMember(kickerUserId) : undefined; + const memberName = kickerMember?.name ?? kickerUserId; const reason = myMember.events.member?.getContent().reason; return { memberName, reason }; } @@ -559,7 +561,7 @@ export default class RoomPreviewBar extends React.Component { "%(errcode)s was returned while trying to access the room or space. " + "If you think you're seeing this message in error, please " + "submit a bug report.", - { errcode: this.props.error.errcode }, + { errcode: String(this.props.error?.errcode) }, { issueLink: (label) => ( { private headerButton = createRef(); private sublistRef = createRef(); private tilesRef = createRef(); - private dispatcherRef: string; + private dispatcherRef?: string; private layout: ListLayout; private heightAtStart: number; private notificationState: ListNotificationState; @@ -257,7 +257,7 @@ export default class RoomSublist extends React.Component { } public componentWillUnmount(): void { - defaultDispatcher.unregister(this.dispatcherRef); + if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); RoomListStore.instance.off(LISTS_LOADING_EVENT, this.onListsLoading); this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent); diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx index dc6c4fd34d..1e14164620 100644 --- a/src/components/views/rooms/Stickerpicker.tsx +++ b/src/components/views/rooms/Stickerpicker.tsx @@ -23,7 +23,7 @@ import AppTile from "../elements/AppTile"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import AccessibleButton from "../elements/AccessibleButton"; -import WidgetUtils, { IWidgetEvent } from "../../../utils/WidgetUtils"; +import WidgetUtils, { UserWidget } from "../../../utils/WidgetUtils"; import PersistedElement from "../elements/PersistedElement"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import ContextMenu, { ChevronFace } from "../../structures/ContextMenu"; @@ -53,7 +53,7 @@ interface IProps { interface IState { imError: string | null; - stickerpickerWidget: IWidgetEvent | null; + stickerpickerWidget: UserWidget | null; widgetId: string | null; } @@ -62,7 +62,7 @@ export default class Stickerpicker extends React.PureComponent { threadId: null, }; - public static currentWidget?: IWidgetEvent; + public static currentWidget?: UserWidget; private dispatcherRef?: string; @@ -252,14 +252,14 @@ export default class Stickerpicker extends React.PureComponent { // Render content from multiple stickerpack sources, each within their // own iframe, within the stickerpicker UI element. const stickerpickerWidget = this.state.stickerpickerWidget; - let stickersContent; + let stickersContent: JSX.Element | undefined; // Use a separate ReactDOM tree to render the AppTile separately so that it persists and does // not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still // updated. // Load stickerpack content - if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) { + if (!!stickerpickerWidget?.content?.url) { // Set default name stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack"); diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 0a32646878..69e30948b4 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -69,9 +69,9 @@ interface IState { } export default class ChangePassword extends React.Component { - private [FIELD_OLD_PASSWORD]: Field | null; - private [FIELD_NEW_PASSWORD]: Field | null; - private [FIELD_NEW_PASSWORD_CONFIRM]: Field | null; + private [FIELD_OLD_PASSWORD]: Field | null = null; + private [FIELD_NEW_PASSWORD]: Field | null = null; + private [FIELD_NEW_PASSWORD_CONFIRM]: Field | null = null; public static defaultProps: Partial = { onFinished() {}, diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index ee69c5cd92..16e9f1ed56 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -43,7 +43,7 @@ interface IState { } export default class IntegrationManager extends React.Component { - private dispatcherRef: string; + private dispatcherRef?: string; public static defaultProps: Partial = { connected: true, @@ -60,7 +60,7 @@ export default class IntegrationManager extends React.Component } public componentWillUnmount(): void { - dis.unregister(this.dispatcherRef); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); } diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index 6e1e6b8e35..8cc3444c3e 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -81,7 +81,7 @@ interface IState { } export default class SetIdServer extends React.Component { - private dispatcherRef: string; + private dispatcherRef?: string; public constructor(props: IProps) { super(props); @@ -108,7 +108,7 @@ export default class SetIdServer extends React.Component { } public componentWillUnmount(): void { - dis.unregister(this.dispatcherRef); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); } private onAction = (payload: ActionPayload): void => { diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts index e9cade175f..879e7169c4 100644 --- a/src/customisations/Media.ts +++ b/src/customisations/Media.ts @@ -20,6 +20,7 @@ import { Optional } from "matrix-events-sdk"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { IMediaEventContent, IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent"; +import { UserFriendlyError } from "../languageHandler"; // Populate this class with the details of your customisations when copying it. @@ -141,7 +142,11 @@ export class Media { * @returns {Promise} Resolves to the server's response for chaining. */ public downloadSource(): Promise { - return fetch(this.srcHttp); + const src = this.srcHttp; + if (!src) { + throw new UserFriendlyError("Failed to download source media, no source url was found"); + } + return fetch(src); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 277da44a9c..9a6b076cfd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1130,6 +1130,7 @@ "You can use /help to list available commands. Did you mean to send this as a message?": "You can use /help to list available commands. Did you mean to send this as a message?", "Hint: Begin your message with // to start it with a slash.": "Hint: Begin your message with // to start it with a slash.", "Send as message": "Send as message", + "Failed to download source media, no source url was found": "Failed to download source media, no source url was found", "Audio devices": "Audio devices", "Mute microphone": "Mute microphone", "Unmute microphone": "Unmute microphone", @@ -2975,6 +2976,7 @@ "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.", "Email (optional)": "Email (optional)", "Please fill why you're reporting.": "Please fill why you're reporting.", + "Unable to create room with moderation bot": "Unable to create room with moderation bot", "Ignore user": "Ignore user", "Check if you want to hide all current and future messages from this user.": "Check if you want to hide all current and future messages from this user.", "What this user is writing is wrong.\nThis will be reported to the room moderators.": "What this user is writing is wrong.\nThis will be reported to the room moderators.", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 7713e3f005..2f8c80023f 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -150,8 +150,8 @@ export class RoomViewStore extends EventEmitter { // another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests. private state = utils.deepCopy(INITIAL_STATE); - private dis: MatrixDispatcher; - private dispatchToken: string; + private dis?: MatrixDispatcher; + private dispatchToken?: string; public constructor(dis: MatrixDispatcher, private readonly stores: SdkContextClass) { super(); @@ -217,7 +217,7 @@ export class RoomViewStore extends EventEmitter { // Fired so we can reduce dependency on event emitters to this store, which is relatively // central to the application and can easily cause import cycles. - this.dis.dispatch({ + this.dis?.dispatch({ action: Action.ActiveRoomChanged, oldRoomId: lastRoomId, newRoomId: this.state.roomId, @@ -343,7 +343,7 @@ export class RoomViewStore extends EventEmitter { // can happen when performing a search across all rooms. Persist the data from this event for both // room and search timeline rendering types, search will get auto-closed by RoomView at this time. if (payload.event && payload.event.getRoomId() !== this.state.roomId) { - this.dis.dispatch({ + this.dis?.dispatch({ action: Action.ViewRoom, room_id: payload.event.getRoomId(), replyingToEvent: payload.event, @@ -414,7 +414,7 @@ export class RoomViewStore extends EventEmitter { return; } // Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now - this.dis.dispatch({ + this.dis?.dispatch({ ...payload, }); return; @@ -455,7 +455,7 @@ export class RoomViewStore extends EventEmitter { this.setState(newState); if (payload.auto_join) { - this.dis.dispatch({ + this.dis?.dispatch({ ...payload, action: Action.JoinRoom, roomId: payload.room_id, @@ -493,7 +493,7 @@ export class RoomViewStore extends EventEmitter { roomId = result.room_id; } catch (err) { logger.error("RVS failed to get room id for alias: ", err); - this.dis.dispatch({ + this.dis?.dispatch({ action: Action.ViewRoomError, room_id: null, room_alias: payload.room_alias, @@ -504,7 +504,7 @@ export class RoomViewStore extends EventEmitter { } // Re-fire the payload with the newly found room_id - this.dis.dispatch({ + this.dis?.dispatch({ ...payload, room_id: roomId, }); @@ -553,13 +553,13 @@ export class RoomViewStore extends EventEmitter { // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. - this.dis.dispatch({ + this.dis?.dispatch({ action: Action.JoinRoomReady, roomId: roomId!, metricsTrigger: payload.metricsTrigger, }); } catch (err) { - this.dis.dispatch({ + this.dis?.dispatch({ action: Action.JoinRoomError, roomId, err, @@ -648,7 +648,7 @@ export class RoomViewStore extends EventEmitter { */ public resetDispatcher(dis: MatrixDispatcher): void { if (this.dispatchToken) { - this.dis.unregister(this.dispatchToken); + this.dis?.unregister(this.dispatchToken); } this.dis = dis; if (dis) { diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index 353d361795..9e7505d495 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -44,7 +44,7 @@ export enum Phase { } export class SetupEncryptionStore extends EventEmitter { - private started: boolean; + private started?: boolean; public phase: Phase; public verificationRequest: VerificationRequest | null = null; public backupInfo: IKeyBackupInfo | null = null; @@ -52,7 +52,7 @@ export class SetupEncryptionStore extends EventEmitter { public keyId: string | null = null; // Descriptor of the key that the secrets we want are encrypted with public keyInfo: ISecretStorageKeyInfo | null = null; - public hasDevicesToVerifyAgainst: boolean; + public hasDevicesToVerifyAgainst?: boolean; public static sharedInstance(): SetupEncryptionStore { if (!window.mxSetupEncryptionStore) window.mxSetupEncryptionStore = new SetupEncryptionStore(); diff --git a/src/stores/TypingStore.ts b/src/stores/TypingStore.ts index f76f4c0949..8346d12a75 100644 --- a/src/stores/TypingStore.ts +++ b/src/stores/TypingStore.ts @@ -32,7 +32,7 @@ export default class TypingStore { userTimer: Timer; serverTimer: Timer; }; - }; + } = {}; public constructor(private readonly context: SdkContextClass) { this.reset(); @@ -68,7 +68,7 @@ export default class TypingStore { if (threadId) return; let currentTyping = this.typingStates[roomId]; - if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) { + if ((!isTyping && !currentTyping) || currentTyping?.isTyping === isTyping) { // No change in state, so don't do anything. We'll let the timer run its course. return; } diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index da5f956bbe..90f1d32ff1 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -157,9 +157,9 @@ export class ElementWidget extends Widget { export class StopGapWidget extends EventEmitter { private client: MatrixClient; - private messaging: ClientWidgetApi | null; + private messaging: ClientWidgetApi | null = null; private mockWidget: ElementWidget; - private scalarToken: string; + private scalarToken?: string; private roomId?: string; private kind: WidgetKind; private readonly virtual: boolean; diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts index 37c0f873bc..5aab198e62 100644 --- a/src/utils/DMRoomMap.ts +++ b/src/utils/DMRoomMap.ts @@ -39,7 +39,7 @@ export default class DMRoomMap { private roomToUser: { [key: string]: string } | null = null; private userToRooms: { [key: string]: string[] } | null = null; private hasSentOutPatchDirectAccountDataPatch: boolean; - private mDirectEvent: { [key: string]: string[] }; + private mDirectEvent!: { [key: string]: string[] }; public constructor(private readonly matrixClient: MatrixClient) { // see onAccountData diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts index 00d10c9d3c..2c4f01b011 100644 --- a/src/utils/WidgetUtils.ts +++ b/src/utils/WidgetUtils.ts @@ -47,7 +47,11 @@ export interface IWidgetEvent { sender: string; // eslint-disable-next-line camelcase state_key: string; - content: Partial; + content: IApp; +} + +export interface UserWidget extends Omit { + content: IWidget & Partial; } export default class WidgetUtils { @@ -254,6 +258,7 @@ export default class WidgetUtils { const userId = client.getSafeUserId(); const content = { + id: widgetId, type: widgetType.preferred, url: widgetUrl, name: widgetName, @@ -354,7 +359,7 @@ export default class WidgetUtils { * Get user specific widgets (not linked to a specific room) * @return {object} Event content object containing current / active user widgets */ - public static getUserWidgets(): Record { + public static getUserWidgets(): Record { const client = MatrixClientPeg.get(); if (!client) { throw new Error("User not logged in"); @@ -370,7 +375,7 @@ export default class WidgetUtils { * Get user specific widgets (not linked to a specific room) as an array * @return {[object]} Array containing current / active user widgets */ - public static getUserWidgetsArray(): IWidgetEvent[] { + public static getUserWidgetsArray(): UserWidget[] { return Object.values(WidgetUtils.getUserWidgets()); } @@ -378,18 +383,18 @@ export default class WidgetUtils { * Get active stickerpicker widgets (stickerpickers are user widgets by nature) * @return {[object]} Array containing current / active stickerpicker widgets */ - public static getStickerpickerWidgets(): IWidgetEvent[] { + public static getStickerpickerWidgets(): UserWidget[] { const widgets = WidgetUtils.getUserWidgetsArray(); - return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker"); + return widgets.filter((widget) => widget.content?.type === "m.stickerpicker"); } /** * Get all integration manager widgets for this user. * @returns {Object[]} An array of integration manager user widgets. */ - public static getIntegrationManagerWidgets(): IWidgetEvent[] { + public static getIntegrationManagerWidgets(): UserWidget[] { const widgets = WidgetUtils.getUserWidgetsArray(); - return widgets.filter((w) => w.content && w.content.type === "m.integration_manager"); + return widgets.filter((w) => w.content?.type === "m.integration_manager"); } public static getRoomWidgetsOfType(room: Room, type: WidgetType): MatrixEvent[] { diff --git a/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts b/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts index a05ce956c1..ab4daae830 100644 --- a/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts +++ b/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts @@ -126,7 +126,7 @@ export class VoiceBroadcastRecorder return; } - this.setCurrentChunkLength(this.voiceRecording.recorderSeconds - this.previousChunkEndTimePosition); + this.setCurrentChunkLength(this.voiceRecording.recorderSeconds! - this.previousChunkEndTimePosition); this.handleData(dataArray); }; @@ -154,7 +154,7 @@ export class VoiceBroadcastRecorder return null; } - const currentRecorderTime = this.voiceRecording.recorderSeconds; + const currentRecorderTime = this.voiceRecording.recorderSeconds!; const payload: ChunkRecordedPayload = { buffer: concat(this.opusHead!, this.opusTags!, this.chunkBuffer), length: this.getCurrentChunkLength(), diff --git a/test/settings/watchers/ThemeWatcher-test.tsx b/test/settings/watchers/ThemeWatcher-test.tsx index 48981a4038..3af6b42430 100644 --- a/test/settings/watchers/ThemeWatcher-test.tsx +++ b/test/settings/watchers/ThemeWatcher-test.tsx @@ -21,8 +21,8 @@ import { SettingLevel } from "../../../src/settings/SettingLevel"; function makeMatchMedia(values: any) { class FakeMediaQueryList { matches: false; - media: null; - onchange: null; + media?: null; + onchange?: null; addListener() {} removeListener() {} addEventListener() {}