From 59feff376306c64e94c8c8606e8252aa0863904c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 11:49:15 +0100 Subject: [PATCH 1/7] Silence RoomListStore possible memory leak warning --- src/stores/room-list/RoomListStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index e26c80bb2d..5d26056a7d 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -73,6 +73,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { constructor() { super(defaultDispatcher); + this.setMaxListeners(20); // CustomRoomTagStore + RoomList + LeftPanel + 8xRoomSubList + spares } private setupWatchers() { From b8ac40ae55a169dc9c27cabeaeb1fe5e21f99f2f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 11:49:44 +0100 Subject: [PATCH 2/7] Fix React missing key error --- src/components/views/rooms/EventTile.tsx | 31 ++++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index b5a4bc41db..14eab5da2e 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1152,11 +1152,11 @@ export default class EventTile extends React.Component { "aria-live": ariaLive, "aria-atomic": true, "data-scroll-tokens": scrollToken, - }, [ - ircTimestamp, - avatar, - sender, - ircPadlock, + }, <> + { ircTimestamp } + { avatar } + { sender } + { ircPadlock }
{ groupTimestamp } { groupPadlock } @@ -1169,8 +1169,8 @@ export default class EventTile extends React.Component { replacingEventId={this.props.replacingEventId} showUrlPreview={false} /> -
, - ]); + + ); } default: { const thread = ReplyThread.makeThread( @@ -1193,10 +1193,10 @@ export default class EventTile extends React.Component { "data-scroll-tokens": scrollToken, "onMouseEnter": () => this.setState({ hover: true }), "onMouseLeave": () => this.setState({ hover: false }), - }, [ - ircTimestamp, - sender, - ircPadlock, + }, <> + { ircTimestamp } + { sender } + { ircPadlock }
{ groupTimestamp } { groupPadlock } @@ -1214,11 +1214,10 @@ export default class EventTile extends React.Component { { keyRequestInfo } { reactionsRow } { actionBar } -
, - msgOption, - avatar, - - ]) + + { msgOption } + { avatar } + ) ); } } From 1eaf6dd4ed260f64b993c6be82a261e466e79da6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 11:49:55 +0100 Subject: [PATCH 3/7] Improve TS in SenderProfile --- .../views/messages/SenderProfile.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index bdae9cec4a..5198effb32 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -15,12 +15,14 @@ */ import React from 'react'; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + import Flair from '../elements/Flair'; import FlairStore from '../../../stores/FlairStore'; import { getUserNameColorClass } from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { MsgType } from "matrix-js-sdk/lib/@types/event"; interface IProps { mxEvent: MatrixEvent; @@ -50,7 +52,7 @@ export default class SenderProfile extends React.Component { componentDidMount() { this.unmounted = false; - this._updateRelatedGroups(); + this.updateRelatedGroups(); if (this.state.userGroups.length === 0) { this.getPublicisedGroups(); @@ -64,7 +66,7 @@ export default class SenderProfile extends React.Component { this.context.removeListener('RoomState.events', this.onRoomStateEvents); } - async getPublicisedGroups() { + private async getPublicisedGroups() { if (!this.unmounted) { const userGroups = await FlairStore.getPublicisedGroupsCached( this.context, this.props.mxEvent.getSender(), @@ -73,15 +75,15 @@ export default class SenderProfile extends React.Component { } } - onRoomStateEvents = event => { + private onRoomStateEvents = (event: MatrixEvent) => { if (event.getType() === 'm.room.related_groups' && event.getRoomId() === this.props.mxEvent.getRoomId() ) { - this._updateRelatedGroups(); + this.updateRelatedGroups(); } }; - _updateRelatedGroups() { + private updateRelatedGroups() { if (this.unmounted) return; const room = this.context.getRoom(this.props.mxEvent.getRoomId()); if (!room) return; @@ -92,7 +94,7 @@ export default class SenderProfile extends React.Component { }); } - _getDisplayedGroups(userGroups, relatedGroups) { + private getDisplayedGroups(userGroups?: string[], relatedGroups?: string[]) { let displayedGroups = userGroups || []; if (relatedGroups && relatedGroups.length > 0) { displayedGroups = relatedGroups.filter((groupId) => { @@ -113,7 +115,7 @@ export default class SenderProfile extends React.Component { const displayName = mxEvent.sender?.rawDisplayName || mxEvent.getSender() || ""; const mxid = mxEvent.sender?.userId || mxEvent.getSender() || ""; - if (msgtype === 'm.emote') { + if (msgtype === MsgType.Emote) { return null; // emote message must include the name so don't duplicate it } @@ -128,7 +130,7 @@ export default class SenderProfile extends React.Component { let flair; if (this.props.enableFlair) { - const displayedGroups = this._getDisplayedGroups( + const displayedGroups = this.getDisplayedGroups( this.state.userGroups, this.state.relatedGroups, ); From e9d56d4f135c7e61df1434722aed997fdd16c70d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 12:10:01 +0100 Subject: [PATCH 4/7] Fix possible uncaught exception for getUrlPreview which would cause 0 url previews if one url was faulty --- src/components/views/rooms/LinkPreviewGroup.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/LinkPreviewGroup.tsx b/src/components/views/rooms/LinkPreviewGroup.tsx index 2541b2e375..c9842bdd33 100644 --- a/src/components/views/rooms/LinkPreviewGroup.tsx +++ b/src/components/views/rooms/LinkPreviewGroup.tsx @@ -40,10 +40,12 @@ const LinkPreviewGroup: React.FC = ({ links, mxEvent, onCancelClick, onH const ts = mxEvent.getTs(); const previews = useAsyncMemo<[string, IPreviewUrlResponse][]>(async () => { - return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(link => { - return cli.getUrlPreview(link, ts).then(preview => [link, preview], error => { + return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => { + try { + return [link, await cli.getUrlPreview(link, ts)]; + } catch (error) { console.error("Failed to get URL preview: " + error); - }); + } })).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>; }, [links, ts], []); From 7c3c04d340e2cd255e4f5d271a5c80dc870ba82b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 12:10:54 +0100 Subject: [PATCH 5/7] Fix instances of setState calls after unmount --- src/components/structures/RoomView.tsx | 33 +++++++++---------- src/components/structures/TimelinePanel.tsx | 4 +++ .../views/messages/SenderProfile.tsx | 19 ++++------- .../tabs/user/AppearanceUserSettingsTab.tsx | 6 ++++ 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2c118149a0..2d264b00e9 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -916,6 +916,7 @@ export default class RoomView extends React.Component { // called when state.room is first initialised (either at initial load, // after a successful peek, or after we join the room). private onRoomLoaded = (room: Room) => { + if (this.unmounted) return; // Attach a widget store listener only when we get a room WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange); this.onWidgetLayoutChange(); // provoke an update @@ -930,9 +931,9 @@ export default class RoomView extends React.Component { }; private async calculateRecommendedVersion(room: Room) { - this.setState({ - upgradeRecommendation: await room.getRecommendedVersion(), - }); + const upgradeRecommendation = await room.getRecommendedVersion(); + if (this.unmounted) return; + this.setState({ upgradeRecommendation }); } private async loadMembersIfJoined(room: Room) { @@ -1022,23 +1023,19 @@ export default class RoomView extends React.Component { }; private async updateE2EStatus(room: Room) { - if (!this.context.isRoomEncrypted(room.roomId)) { - return; - } - if (!this.context.isCryptoEnabled()) { - // If crypto is not currently enabled, we aren't tracking devices at all, - // so we don't know what the answer is. Let's error on the safe side and show - // a warning for this case. - this.setState({ - e2eStatus: E2EStatus.Warning, - }); - return; + if (!this.context.isRoomEncrypted(room.roomId)) return; + + // If crypto is not currently enabled, we aren't tracking devices at all, + // so we don't know what the answer is. Let's error on the safe side and show + // a warning for this case. + let e2eStatus = E2EStatus.Warning; + if (this.context.isCryptoEnabled()) { + /* At this point, the user has encryption on and cross-signing on */ + e2eStatus = await shieldStatusForRoom(this.context, room); } - /* At this point, the user has encryption on and cross-signing on */ - this.setState({ - e2eStatus: await shieldStatusForRoom(this.context, room), - }); + if (this.unmounted) return; + this.setState({ e2eStatus }); } private onAccountData = (event: MatrixEvent) => { diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 85a048e9b8..c21aac790b 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1051,6 +1051,8 @@ class TimelinePanel extends React.Component { { windowLimit: this.props.timelineCap }); const onLoaded = () => { + if (this.unmounted) return; + // clear the timeline min-height when // (re)loading the timeline if (this.messagePanel.current) { @@ -1092,6 +1094,8 @@ class TimelinePanel extends React.Component { }; const onError = (error) => { + if (this.unmounted) return; + this.setState({ timelineLoading: false }); console.error( `Error loading timeline panel at ${eventId}: ${error}`, diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index 5198effb32..d62c10427d 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -38,7 +38,7 @@ interface IState { @replaceableComponent("views.messages.SenderProfile") export default class SenderProfile extends React.Component { static contextType = MatrixClientContext; - private unmounted: boolean; + private unmounted = false; constructor(props: IProps) { super(props); @@ -51,7 +51,6 @@ export default class SenderProfile extends React.Component { } componentDidMount() { - this.unmounted = false; this.updateRelatedGroups(); if (this.state.userGroups.length === 0) { @@ -67,30 +66,24 @@ export default class SenderProfile extends React.Component { } private async getPublicisedGroups() { - if (!this.unmounted) { - const userGroups = await FlairStore.getPublicisedGroupsCached( - this.context, this.props.mxEvent.getSender(), - ); - this.setState({ userGroups }); - } + const userGroups = await FlairStore.getPublicisedGroupsCached(this.context, this.props.mxEvent.getSender()); + if (this.unmounted) return; + this.setState({ userGroups }); } private onRoomStateEvents = (event: MatrixEvent) => { - if (event.getType() === 'm.room.related_groups' && - event.getRoomId() === this.props.mxEvent.getRoomId() - ) { + if (event.getType() === 'm.room.related_groups' && event.getRoomId() === this.props.mxEvent.getRoomId()) { this.updateRelatedGroups(); } }; private updateRelatedGroups() { - if (this.unmounted) return; const room = this.context.getRoom(this.props.mxEvent.getRoomId()); if (!room) return; const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', ''); this.setState({ - relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [], + relatedGroups: relatedGroupsEvent?.getContent().groups || [], }); } diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 17aa9e5561..a94821e94a 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -76,6 +76,7 @@ export default class AppearanceUserSettingsTab extends React.Component Date: Thu, 15 Jul 2021 12:18:17 +0100 Subject: [PATCH 6/7] improve types --- src/TextForEvent.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index ef24fb8e48..95341705bf 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -32,7 +32,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; // any text to display at all. For this reason they return deferred values // to avoid the expense of looking up translations when they're not needed. -function textForMemberEvent(ev): () => string | null { +function textForMemberEvent(ev: MatrixEvent): () => string | null { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender ? ev.sender.name : ev.getSender(); const targetName = ev.target ? ev.target.name : ev.getStateKey(); @@ -127,7 +127,7 @@ function textForMemberEvent(ev): () => string | null { } } -function textForTopicEvent(ev): () => string | null { +function textForTopicEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { senderDisplayName, @@ -135,7 +135,7 @@ function textForTopicEvent(ev): () => string | null { }); } -function textForRoomNameEvent(ev): () => string | null { +function textForRoomNameEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { @@ -154,12 +154,12 @@ function textForRoomNameEvent(ev): () => string | null { }); } -function textForTombstoneEvent(ev): () => string | null { +function textForTombstoneEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t('%(senderDisplayName)s upgraded this room.', { senderDisplayName }); } -function textForJoinRulesEvent(ev): () => string | null { +function textForJoinRulesEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().join_rule) { case "public": @@ -179,7 +179,7 @@ function textForJoinRulesEvent(ev): () => string | null { } } -function textForGuestAccessEvent(ev): () => string | null { +function textForGuestAccessEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().guest_access) { case "can_join": @@ -195,7 +195,7 @@ function textForGuestAccessEvent(ev): () => string | null { } } -function textForRelatedGroupsEvent(ev): () => string | null { +function textForRelatedGroupsEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const groups = ev.getContent().groups || []; const prevGroups = ev.getPrevContent().groups || []; @@ -225,7 +225,7 @@ function textForRelatedGroupsEvent(ev): () => string | null { } } -function textForServerACLEvent(ev): () => string | null { +function textForServerACLEvent(ev: MatrixEvent): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); const current = ev.getContent(); @@ -255,7 +255,7 @@ function textForServerACLEvent(ev): () => string | null { return getText; } -function textForMessageEvent(ev): () => string | null { +function textForMessageEvent(ev: MatrixEvent): () => string | null { return () => { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); let message = senderDisplayName + ': ' + ev.getContent().body; @@ -268,7 +268,7 @@ function textForMessageEvent(ev): () => string | null { }; } -function textForCanonicalAliasEvent(ev): () => string | null { +function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null { const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAlias = ev.getPrevContent().alias; const oldAltAliases = ev.getPrevContent().alt_aliases || []; @@ -682,7 +682,7 @@ for (const evType of ALL_RULE_TYPES) { stateHandlers[evType] = textForMjolnirEvent; } -export function hasText(ev): boolean { +export function hasText(ev: MatrixEvent): boolean { const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; return Boolean(handler?.(ev)); } From 20e0356eb1b7e6623325c2be5b2ba94bd5e168bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Jul 2021 12:25:26 +0100 Subject: [PATCH 7/7] why do my IDE be dumb --- src/components/views/messages/SenderProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index d62c10427d..d4b74db6d0 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -16,13 +16,13 @@ import React from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { MsgType } from "matrix-js-sdk/src/@types/event"; import Flair from '../elements/Flair'; import FlairStore from '../../../stores/FlairStore'; import { getUserNameColorClass } from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { MsgType } from "matrix-js-sdk/lib/@types/event"; interface IProps { mxEvent: MatrixEvent;