diff --git a/src/actions/MatrixActionCreators.ts b/src/actions/MatrixActionCreators.ts index 70b86f8ee5e..72834782d69 100644 --- a/src/actions/MatrixActionCreators.ts +++ b/src/actions/MatrixActionCreators.ts @@ -17,7 +17,7 @@ limitations under the License. import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; -import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; +import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import dis from "../dispatcher/dispatcher"; import { ActionPayload } from "../dispatcher/payloads"; @@ -160,7 +160,7 @@ function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, } /** - * @typedef RoomTimelineAction + * @typedef IRoomTimelineActionPayload * @type {Object} * @property {string} action 'MatrixActions.Room.timeline'. * @property {boolean} isLiveEvent whether the event was attached to a @@ -169,6 +169,13 @@ function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, * event was attached to a timeline in the set of unfiltered timelines. * @property {Room} room the Room whose tags changed. */ +export interface IRoomTimelineActionPayload extends Pick { + action: 'MatrixActions.Room.timeline'; + event: MatrixEvent; + room: Room | null; + isLiveEvent?: boolean; + isLiveUnfilteredRoomTimelineEvent: boolean; +} /** * Create a MatrixActions.Room.timeline action that represents a @@ -177,7 +184,7 @@ function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, * * @param {MatrixClient} matrixClient the matrix client. * @param {MatrixEvent} timelineEvent the event that was added/removed. - * @param {Room} room the Room that was stored. + * @param {?Room} room the Room that was stored. * @param {boolean} toStartOfTimeline whether the event is being added * to the start (and not the end) of the timeline. * @param {boolean} removed whether the event was removed from the @@ -186,25 +193,22 @@ function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, * @param {boolean} data.liveEvent whether the event is a live event, * belonging to a live timeline. * @param {EventTimeline} data.timeline the timeline being altered. - * @returns {RoomTimelineAction} an action of type `MatrixActions.Room.timeline`. + * @returns {IRoomTimelineActionPayload} an action of type `MatrixActions.Room.timeline`. */ function createRoomTimelineAction( matrixClient: MatrixClient, timelineEvent: MatrixEvent, - room: Room, + room: Room | null, toStartOfTimeline: boolean, removed: boolean, - data: { - liveEvent: boolean; - timeline: EventTimeline; - }, -): ActionPayload { + data: IRoomTimelineData, +): IRoomTimelineActionPayload { return { action: 'MatrixActions.Room.timeline', event: timelineEvent, isLiveEvent: data.liveEvent, - isLiveUnfilteredRoomTimelineEvent: - room && data.timeline.getTimelineSet() === room.getUnfilteredTimelineSet(), + isLiveUnfilteredRoomTimelineEvent: room && data.timeline.getTimelineSet() === room.getUnfilteredTimelineSet(), + room, }; } diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 2c5241c519c..ffad0d5d05c 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -23,7 +23,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomState } from "matrix-js-sdk/src/models/room-state"; -import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; +import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import { MatrixClientPeg } from '../MatrixClientPeg'; import QueryMatcher from './QueryMatcher'; @@ -42,11 +42,6 @@ const USER_REGEX = /\B@\S*/g; // to allow you to tab-complete /mat into /(matthew) const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g; -interface IRoomTimelineData { - timeline: EventTimeline; - liveEvent?: boolean; -} - export default class UserProvider extends AutocompleteProvider { matcher: QueryMatcher; users: RoomMember[]; @@ -78,12 +73,12 @@ export default class UserProvider extends AutocompleteProvider { private onRoomTimeline = ( ev: MatrixEvent, - room: Room, + room: Room | null, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData, ) => { - if (!room) return; + if (!room) return; // notification timeline, we'll get this event again with a room specific timeline if (removed) return; if (room.roomId !== this.room.roomId) return; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index c77734db396..4062182266d 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import { Filter } from 'matrix-js-sdk/src/filter'; -import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set"; +import { EventTimelineSet, IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import { Direction } from "matrix-js-sdk/src/models/event-timeline"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from 'matrix-js-sdk/src/models/room'; @@ -62,7 +62,13 @@ class FilePanel extends React.Component { timelineSet: null, }; - private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: true, removed: true, data: any): void => { + private onRoomTimeline = ( + ev: MatrixEvent, + room: Room | null, + toStartOfTimeline: boolean, + removed: boolean, + data: IRoomTimelineData, + ): void => { if (room?.roomId !== this.props?.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f72b65a9435..5eb9ec65db1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -934,10 +934,10 @@ export class RoomView extends React.Component { } }; - private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed, data) => { + private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => { if (this.unmounted) return; - // ignore events for other rooms + // ignore events for other rooms or the notification timeline set if (!room || room.roomId !== this.state.room?.roomId) return; // ignore events from filtered timelines diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 1a4fbc88aff..e9ed951f429 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -18,12 +18,12 @@ import React, { createRef, ReactNode, SyntheticEvent } from 'react'; import ReactDOM from "react-dom"; import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set"; +import { EventTimelineSet, IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import { Direction, EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; import { TimelineWindow } from "matrix-js-sdk/src/timeline-window"; import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event'; import { SyncState } from 'matrix-js-sdk/src/sync'; -import { RoomMember } from 'matrix-js-sdk'; +import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; import { debounce } from 'lodash'; import { logger } from "matrix-js-sdk/src/logger"; @@ -526,13 +526,10 @@ class TimelinePanel extends React.Component { private onRoomTimeline = ( ev: MatrixEvent, - room: Room, + room: Room | null, toStartOfTimeline: boolean, removed: boolean, - data: { - timeline: EventTimeline; - liveEvent?: boolean; - }, + data: IRoomTimelineData, ): void => { // ignore events for other timeline sets if (data.timeline.getTimelineSet() !== this.props.timelineSet) return; diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index 6ba507c0cc2..11aaa40acb6 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -19,6 +19,8 @@ import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import { User } from "matrix-js-sdk/src/models/user"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import RoomAvatar from "./RoomAvatar"; import NotificationBadge from '../rooms/NotificationBadge'; @@ -92,9 +94,9 @@ export default class DecoratedRoomAvatar extends React.PureComponent { + private onRoomTimeline = (ev: MatrixEvent, room: Room | null) => { if (this.isUnmounted) return; + if (this.props.room.roomId !== room?.roomId) return; - // apparently these can happen? - if (!room) return; - if (this.props.room.roomId !== room.roomId) return; - - if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') { + if (ev.getType() === EventType.RoomJoinRules || ev.getType() === EventType.RoomMember) { const newIcon = this.calculateIcon(); if (newIcon !== this.state.icon) { this.setState({ icon: newIcon }); diff --git a/src/components/views/rooms/WhoIsTypingTile.tsx b/src/components/views/rooms/WhoIsTypingTile.tsx index a84d3b8323e..0f5604e4d62 100644 --- a/src/components/views/rooms/WhoIsTypingTile.tsx +++ b/src/components/views/rooms/WhoIsTypingTile.tsx @@ -91,8 +91,8 @@ export default class WhoIsTypingTile extends React.Component { return WhoIsTypingTile.isVisible(this.state); }; - private onRoomTimeline = (event: MatrixEvent, room: Room): void => { - if (room?.roomId === this.props.room?.roomId) { + private onRoomTimeline = (event: MatrixEvent, room: Room | null): void => { + if (room?.roomId === this.props.room.roomId) { const userId = event.getSender(); // remove user from usersTyping const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index 39533a713ec..75a5ede857b 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -19,7 +19,7 @@ import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; import { Direction, EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; import { Room } from 'matrix-js-sdk/src/models/room'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; -import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; +import { EventTimelineSet, IRoomTimelineData } from 'matrix-js-sdk/src/models/event-timeline-set'; import { RoomState } from 'matrix-js-sdk/src/models/room-state'; import { TimelineIndex, TimelineWindow } from 'matrix-js-sdk/src/timeline-window'; import { sleep } from "matrix-js-sdk/src/utils"; @@ -187,17 +187,17 @@ export default class EventIndex extends EventEmitter { */ private onRoomTimeline = async ( ev: MatrixEvent, - room: Room, + room: Room | null, toStartOfTimeline: boolean, removed: boolean, - data: { - liveEvent: boolean; - }, + data: IRoomTimelineData, ) => { + if (!room) return; // notification timeline, we'll get this event again with a room specific timeline + const client = MatrixClientPeg.get(); // We only index encrypted rooms locally. - if (!client.isRoomEncrypted(room.roomId)) return; + if (!client.isRoomEncrypted(ev.getRoomId())) return; if (ev.isRedaction()) { return this.redactEvent(ev); diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index b4a0b19cb40..782b86e4a57 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -71,10 +71,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy this.updateNotificationState(); }; - private handleRoomEventUpdate = (event: MatrixEvent) => { - const roomId = event.getRoomId(); - - if (roomId !== this.room.roomId) return; // ignore - not for us + private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => { + if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline this.updateNotificationState(); }; diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index d639b76c2bf..8488e315065 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -41,6 +41,7 @@ import { SpaceWatcher } from "./SpaceWatcher"; import SpaceStore from "../spaces/SpaceStore"; import { Action } from "../../dispatcher/actions"; import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; +import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators"; interface IState { tagsEnabled?: boolean; @@ -239,15 +240,22 @@ export class RoomListStoreClass extends AsyncStoreWithClient { await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); } else if (payload.action === 'MatrixActions.Room.timeline') { - const eventPayload = (payload); // TODO: Type out the dispatcher types + const eventPayload = payload; - // Ignore non-live events (backfill) - if (!eventPayload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent) return; + // Ignore non-live events (backfill) and notification timeline set events (without a room) + if (!eventPayload.isLiveEvent || + !eventPayload.isLiveUnfilteredRoomTimelineEvent || + !eventPayload.room + ) { + return; + } const roomId = eventPayload.event.getRoomId(); const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom: Room) => { - if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { + if (eventPayload.event.getType() === EventType.RoomTombstone && + eventPayload.event.getStateKey() === '' + ) { const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']); if (newRoom) { // If we have the new room, then the new room check will have seen the predecessor