From be5928cb64218d6f6ca5164a3fa4363b2e5e6ccf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Apr 2023 11:50:42 +0100 Subject: [PATCH] Conform more of the codebase to strictNullChecks (#10672) * Conform more of the codebase to `strictNullChecks` * Iterate * Iterate * Iterate * Iterate * Conform more of the codebase to `strictNullChecks` * Iterate * Update record key --- src/Avatar.ts | 2 +- src/Searching.ts | 23 +++++++++----- src/actions/RoomListActions.ts | 11 ++----- src/components/structures/MessagePanel.tsx | 3 +- src/components/structures/RoomView.tsx | 11 ++++--- src/components/structures/ScrollPanel.tsx | 8 ++--- src/components/structures/UserView.tsx | 3 +- .../views/context_menus/RoomContextMenu.tsx | 6 ++-- .../context_menus/RoomGeneralContextMenu.tsx | 2 +- .../views/dialogs/ReportEventDialog.tsx | 6 ++-- .../security/AccessSecretStorageDialog.tsx | 2 +- .../security/RestoreKeyBackupDialog.tsx | 5 ++- .../views/rooms/MessageComposerButtons.tsx | 21 +++++-------- src/components/views/rooms/RoomPreviewBar.tsx | 10 +++--- .../views/rooms/SearchResultTile.tsx | 2 +- .../views/rooms/SendMessageComposer.tsx | 12 ++++--- .../tabs/room/AdvancedRoomSettingsTab.tsx | 2 +- .../tabs/user/GeneralUserSettingsTab.tsx | 31 ++++++++++++------- src/components/views/voip/AudioFeed.tsx | 4 +-- src/components/views/voip/LegacyCallView.tsx | 20 ++++++------ src/components/views/voip/VideoFeed.tsx | 2 +- src/indexing/EventIndex.ts | 15 +++++---- src/models/Call.ts | 4 +-- src/resizer/sizer.ts | 4 +-- src/stores/BreadcrumbsStore.ts | 2 +- src/stores/OwnBeaconStore.ts | 9 +++--- src/stores/local-echo/RoomEchoChamber.ts | 7 +++-- src/stores/spaces/SpaceStore.ts | 4 +-- src/stores/widgets/StopGapWidgetDriver.ts | 9 ++++-- src/stores/widgets/WidgetLayoutStore.ts | 2 +- src/stores/widgets/WidgetPermissionStore.ts | 2 +- src/utils/SortMembers.ts | 10 +++--- src/utils/notifications.ts | 2 +- .../dialogs/MessageEditHistoryDialog-test.tsx | 2 +- 34 files changed, 143 insertions(+), 115 deletions(-) diff --git a/src/Avatar.ts b/src/Avatar.ts index a023ba0ee7..79254ef1b5 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -26,7 +26,7 @@ import { isLocalRoom } from "./utils/localRoom/isLocalRoom"; // Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember( - member: RoomMember, + member: RoomMember | undefined, width: number, height: number, resizeMethod: ResizeMethod, diff --git a/src/Searching.ts b/src/Searching.ts index 85efeea8c8..25800c8e06 100644 --- a/src/Searching.ts +++ b/src/Searching.ts @@ -176,7 +176,10 @@ async function localSearch( searchArgs.room_id = roomId; } - const localResult = await eventIndex.search(searchArgs); + const localResult = await eventIndex!.search(searchArgs); + if (!localResult) { + throw new Error("Local search failed"); + } searchArgs.next_batch = localResult.next_batch; @@ -225,7 +228,11 @@ async function localPagination(searchResult: ISeshatSearchResults): Promise[2] | null = null; @@ -63,12 +62,8 @@ export default class RoomListActions { newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order); - // If the room was moved "down" (increasing index) in the same list we - // need to use the orders of the tiles with indices shifted by +1 - const offset = newTag === oldTag && oldIndex < newIndex ? 1 : 0; - - const indexBefore = offset + newIndex - 1; - const indexAfter = offset + newIndex; + const indexBefore = newIndex - 1; + const indexAfter = newIndex; const prevOrder = indexBefore <= 0 ? 0 : newList[indexBefore].tags[newTag].order; const nextOrder = indexAfter >= newList.length ? 1 : newList[indexAfter].tags[newTag].order; diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index ed6f778bd5..3d48c925fe 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -883,6 +883,7 @@ export default class MessagePanel extends React.Component { const existingReceipts = receiptsByEvent.get(lastShownEventId) || []; const newReceipts = this.getReadReceiptsForEvent(event); + if (!newReceipts) continue; receiptsByEvent.set(lastShownEventId, existingReceipts.concat(newReceipts)); // Record these receipts along with their last shown event ID for @@ -1218,7 +1219,7 @@ class CreationGrouper extends BaseGrouper { key="roomcreationsummary" events={this.events} onToggle={panel.onHeightChanged} // Update scroll state - summaryMembers={[ev.sender]} + summaryMembers={ev.sender ? [ev.sender] : undefined} summaryText={summaryText} layout={this.panel.props.layout} > diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 4e14733d04..d1d2a5807b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -627,7 +627,7 @@ export class RoomView extends React.Component { mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined, initialEventId: undefined, // default to clearing this, will get set later in the method if needed showRightPanel: this.context.rightPanelStore.isOpenForRoom(roomId), - activeCall: CallStore.instance.getActiveCall(roomId), + activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null, }; if ( @@ -1071,6 +1071,7 @@ export class RoomView extends React.Component { }; private onAction = async (payload: ActionPayload): Promise => { + if (!this.context.client) return; switch (payload.action) { case "message_sent": this.checkDesktopNotifications(); @@ -1228,7 +1229,7 @@ export class RoomView extends React.Component { this.handleEffects(ev); } - if (ev.getSender() !== this.context.client.getSafeUserId()) { + if (this.context.client && ev.getSender() !== this.context.client.getSafeUserId()) { // update unread count when scrolled up if (!this.state.search && this.state.atEndOfLiveTimeline) { // no change @@ -1469,7 +1470,7 @@ export class RoomView extends React.Component { }; private updatePermissions(room: Room): void { - if (room) { + if (room && this.context.client) { const me = this.context.client.getSafeUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me); @@ -1956,6 +1957,8 @@ export class RoomView extends React.Component { } public render(): React.ReactNode { + if (!this.context.client) return null; + if (this.state.room instanceof LocalRoom) { if (this.state.room.state === LocalRoomState.CREATING) { return this.renderLocalRoomCreateLoader(this.state.room); @@ -2064,7 +2067,7 @@ export class RoomView extends React.Component { const inviteEvent = myMember ? myMember.events.member : null; let inviterName = _t("Unknown"); if (inviteEvent) { - inviterName = inviteEvent.sender?.name ?? inviteEvent.getSender(); + inviterName = inviteEvent.sender?.name ?? inviteEvent.getSender()!; } // We deliberately don't try to peek into invites, even if we have permission to peek diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 3b380c1d19..5c2edef0ea 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -786,7 +786,7 @@ export default class ScrollPanel extends React.Component { const scrollState = this.scrollState; const trackedNode = scrollState.trackedNode; - if (!trackedNode?.parentElement) { + if (!trackedNode?.parentElement && this.itemlist.current) { let node: HTMLElement | undefined = undefined; const messages = this.itemlist.current.children; const scrollToken = scrollState.trackedScrollToken; @@ -890,7 +890,7 @@ export default class ScrollPanel extends React.Component { public clearPreventShrinking = (): void => { const messageList = this.itemlist.current; const balanceElement = messageList && messageList.parentElement; - if (balanceElement) balanceElement.style.paddingBottom = null; + if (balanceElement) balanceElement.style.removeProperty("paddingBottom"); this.preventShrinkingState = null; debuglog("prevent shrinking cleared"); }; @@ -904,7 +904,7 @@ export default class ScrollPanel extends React.Component { what it was when marking. */ public updatePreventShrinking = (): void => { - if (this.preventShrinkingState) { + if (this.preventShrinkingState && this.itemlist.current) { const sn = this.getScrollNode(); const scrollState = this.scrollState; const messageList = this.itemlist.current; @@ -922,7 +922,7 @@ export default class ScrollPanel extends React.Component { if (!shouldClear) { const currentOffset = messageList.clientHeight - (offsetNode.offsetTop + offsetNode.clientHeight); const offsetDiff = offsetFromBottom - currentOffset; - if (offsetDiff > 0) { + if (offsetDiff > 0 && balanceElement) { balanceElement.style.paddingBottom = `${offsetDiff}px`; debuglog("update prevent shrinking ", offsetDiff, "px from bottom"); } else if (offsetDiff < 0) { diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx index a5cdb0b584..4cff508dfb 100644 --- a/src/components/structures/UserView.tsx +++ b/src/components/structures/UserView.tsx @@ -79,7 +79,8 @@ export default class UserView extends React.Component { return; } const fakeEvent = new MatrixEvent({ type: "m.room.member", content: profileInfo }); - const member = new RoomMember(null, this.props.userId); + // We pass an empty string room ID here, this is slight abuse of the class to simplify code + const member = new RoomMember("", this.props.userId); member.setMembershipEvent(fakeEvent); this.setState({ member, loading: false }); } diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 4be907c161..4a01503496 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -169,8 +169,8 @@ const RoomContextMenu: React.FC = ({ room, onFinished, ...props }) => { ); const echoChamber = EchoChamber.forRoom(room); - let notificationLabel: string; - let iconClassName: string; + let notificationLabel: string | undefined; + let iconClassName: string | undefined; switch (echoChamber.notificationVolume) { case RoomNotifState.AllMessages: notificationLabel = _t("Default"); @@ -337,7 +337,7 @@ const RoomContextMenu: React.FC = ({ room, onFinished, ...props }) => { const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); const removeTag = isApplied ? tagId : inverseTag; const addTag = isApplied ? null : tagId; - dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, undefined, 0)); + dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0)); } else { logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); } diff --git a/src/components/views/context_menus/RoomGeneralContextMenu.tsx b/src/components/views/context_menus/RoomGeneralContextMenu.tsx index 901ed519b6..0401b20b51 100644 --- a/src/components/views/context_menus/RoomGeneralContextMenu.tsx +++ b/src/components/views/context_menus/RoomGeneralContextMenu.tsx @@ -100,7 +100,7 @@ export const RoomGeneralContextMenu: React.FC = ({ const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); const removeTag = isApplied ? tagId : inverseTag; const addTag = isApplied ? null : tagId; - dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, undefined, 0)); + dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0)); } else { logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); } diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 8eaa64bc34..8ce08208c0 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -260,7 +260,7 @@ export default class ReportEventDialog extends React.Component { // if the user should also be ignored, do that if (this.state.ignoreUserToo) { - await client.setIgnoredUsers([...client.getIgnoredUsers(), ev.getSender()]); + await client.setIgnoredUsers([...client.getIgnoredUsers(), ev.getSender()!]); } this.props.onFinished(true); @@ -309,8 +309,8 @@ export default class ReportEventDialog extends React.Component { // Display report-to-moderator dialog. // We let the user pick a nature. const client = MatrixClientPeg.get(); - const homeServerName = SdkConfig.get("validated_server_config").hsName; - let subtitle; + const homeServerName = SdkConfig.get("validated_server_config")!.hsName; + let subtitle: string; switch (this.state.nature) { case Nature.Disagreement: subtitle = _t( diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index a261e44104..a4ab599563 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -130,7 +130,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent(null const MessageComposerButtons: React.FC = (props: IProps) => { const matrixClient = useContext(MatrixClientContext); - const { room, roomId, narrow } = useContext(RoomContext); + const { room, narrow } = useContext(RoomContext); const isWysiwygLabEnabled = useSettingValue("feature_wysiwyg_composer"); - if (props.haveRecording) { + if (!matrixClient || !room || props.haveRecording) { return null; } @@ -93,7 +93,7 @@ const MessageComposerButtons: React.FC = (props: IProps) => { voiceRecordingButton(props, narrow), startVoiceBroadcastButton(props), props.showPollsButton ? pollButton(room, props.relation) : null, - showLocationButton(props, room, roomId, matrixClient), + showLocationButton(props, room, matrixClient), ]; } else { mainButtons = [ @@ -113,7 +113,7 @@ const MessageComposerButtons: React.FC = (props: IProps) => { voiceRecordingButton(props, narrow), startVoiceBroadcastButton(props), props.showPollsButton ? pollButton(room, props.relation) : null, - showLocationButton(props, room, roomId, matrixClient), + showLocationButton(props, room, matrixClient), ]; } @@ -127,7 +127,7 @@ const MessageComposerButtons: React.FC = (props: IProps) => { }); return ( - + {mainButtons} {moreButtons.length > 0 && ( { } } -function showLocationButton( - props: IProps, - room: Room, - roomId: string, - matrixClient: MatrixClient, -): ReactElement | null { - const sender = room.getMember(matrixClient.getUserId()!); +function showLocationButton(props: IProps, room: Room, matrixClient: MatrixClient): ReactElement | null { + const sender = room.getMember(matrixClient.getSafeUserId()); return props.showLocationButton && sender ? ( { const result = await MatrixClientPeg.get().lookupThreePid( "email", this.props.invitedEmail, - identityAccessToken, + identityAccessToken!, ); this.setState({ invitedEmailMxid: result.mxid }); } catch (err) { @@ -243,8 +243,8 @@ export default class RoomPreviewBar extends React.Component { if (!inviteEvent) { return null; } - const inviterUserId = inviteEvent.events.member.getSender(); - return room.currentState.getMember(inviterUserId); + const inviterUserId = inviteEvent.events.member?.getSender(); + return inviterUserId ? room.currentState.getMember(inviterUserId) : null; } private isDMInvite(): boolean { @@ -252,8 +252,8 @@ export default class RoomPreviewBar extends React.Component { if (!myMember) { return false; } - const memberContent = myMember.events.member.getContent(); - return memberContent.membership === "invite" && memberContent.is_direct; + const memberContent = myMember.events.member?.getContent(); + return memberContent?.membership === "invite" && memberContent.is_direct; } private makeScreenAfterLogin(): { screen: string; params: Record } { diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx index be15ea9694..437b13b899 100644 --- a/src/components/views/rooms/SearchResultTile.tsx +++ b/src/components/views/rooms/SearchResultTile.tsx @@ -64,7 +64,7 @@ export default class SearchResultTile extends React.Component { const eventId = resultEvent.getId(); const ts1 = resultEvent.getTs(); - const ret = []; + const ret = []; const layout = SettingsStore.getValue("layout"); const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 24fbf5ccad..700776d54c 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -372,7 +372,10 @@ export class SendMessageComposer extends React.Component= 0; i--) { @@ -443,8 +447,8 @@ export class SendMessageComposer extends React.Component(posthogEvent); @@ -480,7 +484,7 @@ export class SendMessageComposer extends React.Component { const room = MatrixClientPeg.get().getRoom(this.props.roomId); - Modal.createDialog(RoomUpgradeDialog, { room }); + if (room) Modal.createDialog(RoomUpgradeDialog, { room }); }; private onOldRoomClicked = (e: ButtonEvent): void => { diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 233e999afb..0827065fac 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -66,13 +66,20 @@ interface IState { haveIdServer: boolean; serverSupportsSeparateAddAndBind?: boolean; idServerHasUnsignedTerms: boolean; - requiredPolicyInfo: { - // This object is passed along to a component for handling - hasTerms: boolean; - policiesAndServices: ServicePolicyPair[] | null; // From the startTermsFlow callback - agreedUrls: string[] | null; // From the startTermsFlow callback - resolve: ((values: string[]) => void) | null; // Promise resolve function for startTermsFlow callback - }; + requiredPolicyInfo: + | { + // This object is passed along to a component for handling + hasTerms: false; + policiesAndServices: null; // From the startTermsFlow callback + agreedUrls: null; // From the startTermsFlow callback + resolve: null; // Promise resolve function for startTermsFlow callback + } + | { + hasTerms: boolean; + policiesAndServices: ServicePolicyPair[]; + agreedUrls: string[]; + resolve: (values: string[]) => void; + }; emails: IThreepid[]; msisdns: IThreepid[]; loading3pids: boolean; // whether or not the emails and msisdns have been loaded @@ -191,19 +198,19 @@ export default class GeneralUserSettingsTab extends React.Component { - if (!this.state.haveIdServer) { + // By starting the terms flow we get the logic for checking which terms the user has signed + // for free. So we might as well use that for our own purposes. + const idServerUrl = MatrixClientPeg.get().getIdentityServerUrl(); + if (!this.state.haveIdServer || !idServerUrl) { this.setState({ idServerHasUnsignedTerms: false }); return; } - // By starting the terms flow we get the logic for checking which terms the user has signed - // for free. So we might as well use that for our own purposes. - const idServerUrl = MatrixClientPeg.get().getIdentityServerUrl(); const authClient = new IdentityAuthClient(); try { const idAccessToken = await authClient.getAccessToken({ check: false }); await startTermsFlow( - [new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken)], + [new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)], (policiesAndServices, agreedUrls, extraClassNames) => { return new Promise((resolve, reject) => { this.setState({ diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx index d9a36af6cc..e079360b47 100644 --- a/src/components/views/voip/AudioFeed.tsx +++ b/src/components/views/voip/AudioFeed.tsx @@ -62,7 +62,7 @@ export default class AudioFeed extends React.Component { // it fails. // It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID // back to the default after the call is over - Dave - element.setSinkId(audioOutput); + element!.setSinkId(audioOutput); } catch (e) { logger.error("Couldn't set requested audio output device: using default", e); logger.warn("Couldn't set requested audio output device: using default", e); @@ -103,7 +103,7 @@ export default class AudioFeed extends React.Component { if (!element) return; element.pause(); - element.src = null; + element.removeAttribute("src"); // As per comment in componentDidMount, setting the sink ID back to the // default once the call is over makes setSinkId work reliably. - Dave diff --git a/src/components/views/voip/LegacyCallView.tsx b/src/components/views/voip/LegacyCallView.tsx index 86be87608d..5978acb316 100644 --- a/src/components/views/voip/LegacyCallView.tsx +++ b/src/components/views/voip/LegacyCallView.tsx @@ -339,16 +339,17 @@ export default class LegacyCallView extends React.Component { private onCallResumeClick = (): void => { const userFacingRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call); - LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId); + if (userFacingRoomId) LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId); }; private onTransferClick = (): void => { const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(this.props.call.callId); - this.props.call.transferToCall(transfereeCall); + if (transfereeCall) this.props.call.transferToCall(transfereeCall); }; private onHangupClick = (): void => { - LegacyCallHandler.instance.hangupOrReject(LegacyCallHandler.instance.roomIdForCall(this.props.call)); + const roomId = LegacyCallHandler.instance.roomIdForCall(this.props.call); + if (roomId) LegacyCallHandler.instance.hangupOrReject(roomId); }; private onToggleSidebar = (): void => { @@ -451,13 +452,12 @@ export default class LegacyCallView extends React.Component { let holdTransferContent: React.ReactNode; if (transfereeCall) { - const transferTargetRoom = MatrixClientPeg.get().getRoom( - LegacyCallHandler.instance.roomIdForCall(call), - ); + const cli = MatrixClientPeg.get(); + const callRoomId = LegacyCallHandler.instance.roomIdForCall(call); + const transferTargetRoom = callRoomId ? cli.getRoom(callRoomId) : null; const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person"); - const transfereeRoom = MatrixClientPeg.get().getRoom( - LegacyCallHandler.instance.roomIdForCall(transfereeCall), - ); + const transfereeCallRoomId = LegacyCallHandler.instance.roomIdForCall(transfereeCall); + const transfereeRoom = transfereeCallRoomId ? cli.getRoom(transfereeCallRoomId) : null; const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person"); holdTransferContent = ( @@ -579,6 +579,8 @@ export default class LegacyCallView extends React.Component { const callRoomId = LegacyCallHandler.instance.roomIdForCall(call); const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall); const callRoom = callRoomId ? client.getRoom(callRoomId) : null; + if (!callRoom) return null; + const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null; const callViewClasses = classNames({ diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 503c53ec66..c02154936f 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -150,7 +150,7 @@ export default class VideoFeed extends React.PureComponent { if (!element) return; element.pause(); - element.src = null; + element.removeAttribute("src"); // As per comment in componentDidMount, setting the sink ID back to the // default once the call is over makes setSinkId work reliably. - Dave diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index bca7547b6c..84c0a3ec20 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -235,8 +235,11 @@ export default class EventIndex extends EventEmitter { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); if (!indexManager) return; + const associatedId = ev.getAssociatedId(); + if (!associatedId) return; + try { - await indexManager.deleteEvent(ev.getAssociatedId()); + await indexManager.deleteEvent(associatedId); } catch (e) { logger.log("EventIndex: Error deleting event from index", e); } @@ -519,10 +522,10 @@ export default class EventIndex extends EventEmitter { const profiles: Record = {}; stateEvents.forEach((ev) => { - if (ev.event.content && ev.event.content.membership === "join") { - profiles[ev.event.sender] = { - displayname: ev.event.content.displayname, - avatar_url: ev.event.content.avatar_url, + if (ev.getContent().membership === "join") { + profiles[ev.getSender()!] = { + displayname: ev.getContent().displayname, + avatar_url: ev.getContent().avatar_url, }; } }); @@ -733,7 +736,7 @@ export default class EventIndex extends EventEmitter { const matrixEvents = events.map((e) => { const matrixEvent = eventMapper(e.event); - const member = new RoomMember(room.roomId, matrixEvent.getSender()); + const member = new RoomMember(room.roomId, matrixEvent.getSender()!); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the diff --git a/src/models/Call.ts b/src/models/Call.ts index 6f96e9d887..d3b99db284 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -213,7 +213,7 @@ export abstract class Call extends TypedEventEmitter { await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) }); } } else if (payload.action === Action.ViewRoom) { - if (payload.auto_join && !this.matrixClient.getRoom(payload.room_id)) { + if (payload.auto_join && payload.room_id && !this.matrixClient.getRoom(payload.room_id)) { // Queue the room instead of pushing it immediately. We're probably just // waiting for a room join to complete. this.waitingRooms.push({ roomId: payload.room_id, addedTs: Date.now() }); diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index db9e57f46c..2509dc92a3 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -426,6 +426,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { roomId: Room["roomId"], beaconInfoContent: MBeaconInfoEventContent, ): Promise => { + if (!this.matrixClient) return; // explicitly stop any live beacons this user has // to ensure they remain stopped // if the new replacing beacon is redacted @@ -435,7 +436,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { // eslint-disable-next-line camelcase const { event_id } = await doMaybeLocalRoomAction( roomId, - (actualRoomId: string) => this.matrixClient.unstable_createLiveBeacon(actualRoomId, beaconInfoContent), + (actualRoomId: string) => this.matrixClient!.unstable_createLiveBeacon(actualRoomId, beaconInfoContent), this.matrixClient, ); @@ -552,7 +553,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { const updateContent = makeBeaconInfoContent(timeout, live, description, assetType, timestamp); try { - await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent); + await this.matrixClient!.unstable_setLiveBeacon(beacon.roomId, updateContent); // cleanup any errors const hadError = this.beaconUpdateErrors.has(beacon.identifier); if (hadError) { @@ -576,7 +577,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.lastPublishedPositionTimestamp = Date.now(); await Promise.all( this.healthyLiveBeaconIds.map((beaconId) => - this.sendLocationToBeacon(this.beacons.get(beaconId), position), + this.beacons.has(beaconId) ? this.sendLocationToBeacon(this.beacons.get(beaconId)!, position) : null, ), ); }; @@ -589,7 +590,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { private sendLocationToBeacon = async (beacon: Beacon, { geoUri, timestamp }: TimedGeoUri): Promise => { const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId); try { - await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content); + await this.matrixClient!.sendEvent(beacon.roomId, M_BEACON.name, content); this.incrementBeaconLocationPublishErrorCount(beacon.identifier, false); } catch (error) { logger.error(error); diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts index 15a3affdda..2eac117451 100644 --- a/src/stores/local-echo/RoomEchoChamber.ts +++ b/src/stores/local-echo/RoomEchoChamber.ts @@ -27,7 +27,7 @@ export enum CachedRoomKey { NotificationVolume, } -export class RoomEchoChamber extends GenericEchoChamber { +export class RoomEchoChamber extends GenericEchoChamber { private properties = new Map(); public constructor(context: RoomEchoContext) { @@ -67,11 +67,12 @@ export class RoomEchoChamber extends GenericEchoChamber { public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise => { try { - const { rooms } = await this.matrixClient.getRoomHierarchy(space.roomId, limit, 1, true); + const { rooms } = await this.matrixClient!.getRoomHierarchy(space.roomId, limit, 1, true); const viaMap = new EnhancedMap>(); rooms.forEach((room) => { @@ -979,7 +979,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private onRoomState = (ev: MatrixEvent): void => { const room = this.matrixClient?.getRoom(ev.getRoomId()); - if (!room) return; + if (!this.matrixClient || !room) return; switch (ev.getType()) { case EventType.SpaceChild: { diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 4c37ffd84c..9e24126b15 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -274,19 +274,22 @@ export class StopGapWidgetDriver extends WidgetDriver { await Promise.all( Object.entries(contentMap).flatMap(([userId, userContentMap]) => Object.entries(userContentMap).map(async ([deviceId, content]): Promise => { + const devices = deviceInfoMap.get(userId); + if (!devices) return; + if (deviceId === "*") { // Send the message to all devices we have keys for await client.encryptAndSendToDevices( - Array.from(deviceInfoMap.get(userId).values()).map((deviceInfo) => ({ + Array.from(devices.values()).map((deviceInfo) => ({ userId, deviceInfo, })), content, ); - } else { + } else if (devices.has(deviceId)) { // Send the message to a specific device await client.encryptAndSendToDevices( - [{ userId, deviceInfo: deviceInfoMap.get(userId).get(deviceId) }], + [{ userId, deviceInfo: devices.get(deviceId)! }], content, ); } diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index f260895c30..2bfd555ea3 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -363,7 +363,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { } public getContainerWidgets(room: Optional, container: Container): IApp[] { - return this.byRoom.get(room?.roomId)?.get(container)?.ordered || []; + return (room && this.byRoom.get(room.roomId)?.get(container)?.ordered) || []; } public isInContainer(room: Room, widget: IApp, container: Container): boolean { diff --git a/src/stores/widgets/WidgetPermissionStore.ts b/src/stores/widgets/WidgetPermissionStore.ts index 244a95f06c..b6ea52c162 100644 --- a/src/stores/widgets/WidgetPermissionStore.ts +++ b/src/stores/widgets/WidgetPermissionStore.ts @@ -58,7 +58,7 @@ export class WidgetPermissionStore { return OIDCState.Unknown; } - public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string, newState: OIDCState): void { + public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string | undefined, newState: OIDCState): void { const settingsKey = this.packSettingKey(widget, kind, roomId); let currentValues = SettingsStore.getValue<{ diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts index d19b461b1e..8be4e8a939 100644 --- a/src/utils/SortMembers.ts +++ b/src/utils/SortMembers.ts @@ -67,7 +67,7 @@ interface IActivityScore { // We do this by checking every room to see who has sent a message in the last few hours, and giving them // a score which correlates to the freshness of their message. In theory, this results in suggestions // which are closer to "continue this conversation" rather than "this person exists". -export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivityScore | undefined } { +export function buildActivityScores(cli: MatrixClient): { [userId: string]: IActivityScore } { const now = new Date().getTime(); const earliestAgeConsidered = now - 60 * 60 * 1000; // 1 hour ago const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic @@ -75,6 +75,7 @@ export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivi .flatMap((room) => takeRight(room.getLiveTimeline().getEvents(), maxMessagesConsidered)) .filter((ev) => ev.getTs() > earliestAgeConsidered); const senderEvents = groupBy(events, (ev) => ev.getSender()); + // If the iteratee in mapValues returns undefined that key will be removed from the resultant object return mapValues(senderEvents, (events) => { if (!events.length) return; const lastEvent = maxBy(events, (ev) => ev.getTs())!; @@ -87,7 +88,7 @@ export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivi // an approximate maximum for being selected. score: Math.max(1, inverseTime / (15 * 60 * 1000)), // 15min segments to keep scores sane }; - }); + }) as { [key: string]: IActivityScore }; } interface IMemberScore { @@ -96,13 +97,14 @@ interface IMemberScore { numRooms: number; } -export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberScore | undefined } { +export function buildMemberScores(cli: MatrixClient): { [userId: string]: IMemberScore } { const maxConsideredMembers = 200; const consideredRooms = joinedRooms(cli).filter((room) => room.getJoinedMemberCount() < maxConsideredMembers); const memberPeerEntries = consideredRooms.flatMap((room) => room.getJoinedMembers().map((member) => ({ member, roomSize: room.getJoinedMemberCount() })), ); const userMeta = groupBy(memberPeerEntries, ({ member }) => member.userId); + // If the iteratee in mapValues returns undefined that key will be removed from the resultant object return mapValues(userMeta, (roomMemberships) => { if (!roomMemberships.length) return; const maximumPeers = maxConsideredMembers * roomMemberships.length; @@ -112,5 +114,5 @@ export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberSc numRooms: roomMemberships.length, score: Math.max(0, Math.pow(1 - totalPeers / maximumPeers, 5)), }; - }); + }) as { [userId: string]: IMemberScore }; } diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts index eda75bb17a..45ae756604 100644 --- a/src/utils/notifications.ts +++ b/src/utils/notifications.ts @@ -28,7 +28,7 @@ export const deviceNotificationSettingsKeys = [ "audioNotificationsEnabled", ]; -export function getLocalNotificationAccountDataEventType(deviceId: string): string { +export function getLocalNotificationAccountDataEventType(deviceId: string | null): string { return `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`; } diff --git a/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx b/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx index 28f1aa4765..c220e5a0f4 100644 --- a/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx +++ b/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx @@ -43,7 +43,7 @@ describe("", () => { return result; } - function mockEdits(...edits: { msg: string; ts: number | undefined }[]) { + function mockEdits(...edits: { msg: string; ts?: number }[]) { client.relations.mockImplementation(() => Promise.resolve({ events: edits.map(