Correctly handle Room.timeline events which have a nullable Room (#7635)

This commit is contained in:
Michael Telatynski 2022-01-26 13:24:14 +00:00 committed by GitHub
parent d239697384
commit 8e4ced6454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 62 additions and 55 deletions

View file

@ -17,7 +17,7 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room"; 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 dis from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
@ -160,7 +160,7 @@ function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent,
} }
/** /**
* @typedef RoomTimelineAction * @typedef IRoomTimelineActionPayload
* @type {Object} * @type {Object}
* @property {string} action 'MatrixActions.Room.timeline'. * @property {string} action 'MatrixActions.Room.timeline'.
* @property {boolean} isLiveEvent whether the event was attached to a * @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. * event was attached to a timeline in the set of unfiltered timelines.
* @property {Room} room the Room whose tags changed. * @property {Room} room the Room whose tags changed.
*/ */
export interface IRoomTimelineActionPayload extends Pick<ActionPayload, "action"> {
action: 'MatrixActions.Room.timeline';
event: MatrixEvent;
room: Room | null;
isLiveEvent?: boolean;
isLiveUnfilteredRoomTimelineEvent: boolean;
}
/** /**
* Create a MatrixActions.Room.timeline action that represents a * 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 {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} timelineEvent the event that was added/removed. * @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 * @param {boolean} toStartOfTimeline whether the event is being added
* to the start (and not the end) of the timeline. * to the start (and not the end) of the timeline.
* @param {boolean} removed whether the event was removed from the * @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, * @param {boolean} data.liveEvent whether the event is a live event,
* belonging to a live timeline. * belonging to a live timeline.
* @param {EventTimeline} data.timeline the timeline being altered. * @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( function createRoomTimelineAction(
matrixClient: MatrixClient, matrixClient: MatrixClient,
timelineEvent: MatrixEvent, timelineEvent: MatrixEvent,
room: Room, room: Room | null,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: { data: IRoomTimelineData,
liveEvent: boolean; ): IRoomTimelineActionPayload {
timeline: EventTimeline;
},
): ActionPayload {
return { return {
action: 'MatrixActions.Room.timeline', action: 'MatrixActions.Room.timeline',
event: timelineEvent, event: timelineEvent,
isLiveEvent: data.liveEvent, isLiveEvent: data.liveEvent,
isLiveUnfilteredRoomTimelineEvent: isLiveUnfilteredRoomTimelineEvent: room && data.timeline.getTimelineSet() === room.getUnfilteredTimelineSet(),
room && data.timeline.getTimelineSet() === room.getUnfilteredTimelineSet(), room,
}; };
} }

View file

@ -23,7 +23,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { RoomState } from "matrix-js-sdk/src/models/room-state"; 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 { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher'; import QueryMatcher from './QueryMatcher';
@ -42,11 +42,6 @@ const USER_REGEX = /\B@\S*/g;
// to allow you to tab-complete /mat into /(matthew) // to allow you to tab-complete /mat into /(matthew)
const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g; const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
interface IRoomTimelineData {
timeline: EventTimeline;
liveEvent?: boolean;
}
export default class UserProvider extends AutocompleteProvider { export default class UserProvider extends AutocompleteProvider {
matcher: QueryMatcher<RoomMember>; matcher: QueryMatcher<RoomMember>;
users: RoomMember[]; users: RoomMember[];
@ -78,12 +73,12 @@ export default class UserProvider extends AutocompleteProvider {
private onRoomTimeline = ( private onRoomTimeline = (
ev: MatrixEvent, ev: MatrixEvent,
room: Room, room: Room | null,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: IRoomTimelineData, 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 (removed) return;
if (room.roomId !== this.room.roomId) return; if (room.roomId !== this.room.roomId) return;

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { Filter } from 'matrix-js-sdk/src/filter'; 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 { Direction } from "matrix-js-sdk/src/models/event-timeline";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
@ -62,7 +62,13 @@ class FilePanel extends React.Component<IProps, IState> {
timelineSet: null, 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 (room?.roomId !== this.props?.roomId) return;
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;

View file

@ -934,10 +934,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
}; };
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; 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; if (!room || room.roomId !== this.state.room?.roomId) return;
// ignore events from filtered timelines // ignore events from filtered timelines

View file

@ -18,12 +18,12 @@ import React, { createRef, ReactNode, SyntheticEvent } from 'react';
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room"; import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; 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 { Direction, EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window"; import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event'; import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
import { SyncState } from 'matrix-js-sdk/src/sync'; 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 { debounce } from 'lodash';
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -526,13 +526,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
private onRoomTimeline = ( private onRoomTimeline = (
ev: MatrixEvent, ev: MatrixEvent,
room: Room, room: Room | null,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: { data: IRoomTimelineData,
timeline: EventTimeline;
liveEvent?: boolean;
},
): void => { ): void => {
// ignore events for other timeline sets // ignore events for other timeline sets
if (data.timeline.getTimelineSet() !== this.props.timelineSet) return; if (data.timeline.getTimelineSet() !== this.props.timelineSet) return;

View file

@ -19,6 +19,8 @@ import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { User } from "matrix-js-sdk/src/models/user"; import { User } from "matrix-js-sdk/src/models/user";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; 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 RoomAvatar from "./RoomAvatar";
import NotificationBadge from '../rooms/NotificationBadge'; import NotificationBadge from '../rooms/NotificationBadge';
@ -92,9 +94,9 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
} }
private get isPublicRoom(): boolean { private get isPublicRoom(): boolean {
const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", ""); const joinRules = this.props.room.currentState.getStateEvents(EventType.RoomJoinRules, "");
const joinRule = joinRules && joinRules.getContent().join_rule; const joinRule = joinRules && joinRules.getContent().join_rule;
return joinRule === 'public'; return joinRule === JoinRule.Public;
} }
private get dmUser(): User { private get dmUser(): User {
@ -114,14 +116,11 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
} }
} }
private onRoomTimeline = (ev: MatrixEvent, room: Room) => { private onRoomTimeline = (ev: MatrixEvent, room: Room | null) => {
if (this.isUnmounted) return; if (this.isUnmounted) return;
if (this.props.room.roomId !== room?.roomId) return;
// apparently these can happen? if (ev.getType() === EventType.RoomJoinRules || ev.getType() === EventType.RoomMember) {
if (!room) return;
if (this.props.room.roomId !== room.roomId) return;
if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') {
const newIcon = this.calculateIcon(); const newIcon = this.calculateIcon();
if (newIcon !== this.state.icon) { if (newIcon !== this.state.icon) {
this.setState({ icon: newIcon }); this.setState({ icon: newIcon });

View file

@ -91,8 +91,8 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
return WhoIsTypingTile.isVisible(this.state); return WhoIsTypingTile.isVisible(this.state);
}; };
private onRoomTimeline = (event: MatrixEvent, room: Room): void => { private onRoomTimeline = (event: MatrixEvent, room: Room | null): void => {
if (room?.roomId === this.props.room?.roomId) { if (room?.roomId === this.props.room.roomId) {
const userId = event.getSender(); const userId = event.getSender();
// remove user from usersTyping // remove user from usersTyping
const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId);

View file

@ -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 { Direction, EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; 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 { RoomState } from 'matrix-js-sdk/src/models/room-state';
import { TimelineIndex, TimelineWindow } from 'matrix-js-sdk/src/timeline-window'; import { TimelineIndex, TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
import { sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";
@ -187,17 +187,17 @@ export default class EventIndex extends EventEmitter {
*/ */
private onRoomTimeline = async ( private onRoomTimeline = async (
ev: MatrixEvent, ev: MatrixEvent,
room: Room, room: Room | null,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: { data: IRoomTimelineData,
liveEvent: boolean;
},
) => { ) => {
if (!room) return; // notification timeline, we'll get this event again with a room specific timeline
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
// We only index encrypted rooms locally. // We only index encrypted rooms locally.
if (!client.isRoomEncrypted(room.roomId)) return; if (!client.isRoomEncrypted(ev.getRoomId())) return;
if (ev.isRedaction()) { if (ev.isRedaction()) {
return this.redactEvent(ev); return this.redactEvent(ev);

View file

@ -71,10 +71,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.updateNotificationState(); this.updateNotificationState();
}; };
private handleRoomEventUpdate = (event: MatrixEvent) => { private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => {
const roomId = event.getRoomId(); if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline
if (roomId !== this.room.roomId) return; // ignore - not for us
this.updateNotificationState(); this.updateNotificationState();
}; };

View file

@ -41,6 +41,7 @@ import { SpaceWatcher } from "./SpaceWatcher";
import SpaceStore from "../spaces/SpaceStore"; import SpaceStore from "../spaces/SpaceStore";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
interface IState { interface IState {
tagsEnabled?: boolean; tagsEnabled?: boolean;
@ -239,15 +240,22 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange); await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange);
this.updateFn.trigger(); this.updateFn.trigger();
} else if (payload.action === 'MatrixActions.Room.timeline') { } else if (payload.action === 'MatrixActions.Room.timeline') {
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types const eventPayload = <IRoomTimelineActionPayload>payload;
// Ignore non-live events (backfill) // Ignore non-live events (backfill) and notification timeline set events (without a room)
if (!eventPayload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent) return; if (!eventPayload.isLiveEvent ||
!eventPayload.isLiveUnfilteredRoomTimelineEvent ||
!eventPayload.room
) {
return;
}
const roomId = eventPayload.event.getRoomId(); const roomId = eventPayload.event.getRoomId();
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient.getRoom(roomId);
const tryUpdate = async (updatedRoom: Room) => { 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']); const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']);
if (newRoom) { if (newRoom) {
// If we have the new room, then the new room check will have seen the predecessor // If we have the new room, then the new room check will have seen the predecessor