Fix joinRoom failing when the roomviewstore state changes. (#34180)

* Fix joinRoom failing when the roomviewstore state changes.

* drive by type fix

* Help debugging failed dispatches

* Use proper types and include roomId in RoomViewStore tests

* remove focus

* Update apps/web/src/stores/RoomViewStore.tsx

Co-authored-by: R Midhun Suresh <hi@midhun.dev>

* fmt

* Add a test for coverage

---------

Co-authored-by: R Midhun Suresh <hi@midhun.dev>
This commit is contained in:
Will Hunt
2026-07-28 16:41:36 +00:00
committed by GitHub
co-authored by R Midhun Suresh
parent f2cc9353ac
commit ea38041c87
3 changed files with 67 additions and 31 deletions
+30 -14
View File
@@ -148,7 +148,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;
// this is defacto always assigned as `resetDispatcher` is called in the constructor.
private dis!: MatrixDispatcher;
private dispatchToken?: string;
public constructor(
@@ -195,7 +196,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<ActiveRoomChangedPayload>({
this.dis.dispatch<ActiveRoomChangedPayload>({
action: Action.ActiveRoomChanged,
oldRoomId: lastRoomId,
newRoomId: this.state.roomId,
@@ -302,7 +303,7 @@ export class RoomViewStore extends EventEmitter {
// if the room is displayed in a module, we don't want to change the room view
if (roomId && this.isRoomDisplayedInModule(roomId)) return;
this.dis?.dispatch<ViewRoomPayload>({
this.dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: payload.event.getRoomId(),
replyingToEvent: payload.event,
@@ -415,7 +416,7 @@ export class RoomViewStore extends EventEmitter {
await this.stores.slidingSyncManager.setRoomVisible(payload.room_id);
// 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;
@@ -466,7 +467,7 @@ export class RoomViewStore extends EventEmitter {
viaServers: payload.via_servers,
};
}
this.dis?.dispatch<JoinRoomPayload>(joinPayload);
this.dis.dispatch<JoinRoomPayload>(joinPayload);
}
if (room) {
@@ -505,7 +506,7 @@ export class RoomViewStore extends EventEmitter {
viaServers = result.servers;
} catch (err) {
logger.error("RVS failed to get room id for alias: ", err);
this.dis?.dispatch<ViewRoomErrorPayload>({
this.dis.dispatch<ViewRoomErrorPayload>({
action: Action.ViewRoomError,
room_id: null,
room_alias: payload.room_alias,
@@ -516,7 +517,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,
via_servers: viaServers,
@@ -545,9 +546,24 @@ export class RoomViewStore extends EventEmitter {
});
// take a copy of roomAlias, roomId & viaServers as they may change by the time the join is complete
const { roomAlias, roomId = payload.roomId, viaServers = [] } = this.state;
const { roomAlias, viaServers = [] } = this.state;
// fall back to the payload's roomId explicitly since it is always the room we were asked to join
const roomId = this.state.roomId ?? payload.roomId;
// prefer the room alias if we have one as it allows joining over federation even with no viaServers
const address = roomAlias || roomId!;
const address = roomAlias || roomId;
if (!address) {
logger.error("Cannot join room: no room ID or alias to join", payload);
this.dis.dispatch<JoinRoomErrorPayload>({
action: Action.JoinRoomError,
roomId,
err: new UserFriendlyError("room|error_join_unknown", {
cause: new Error("Cannot join room: no room ID or alias to join"),
}),
canAskToJoin: payload.canAskToJoin,
});
return;
}
const joinOpts: IJoinRoomOpts = {
viaServers,
@@ -568,14 +584,14 @@ 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<JoinRoomReadyPayload>({
this.dis.dispatch<JoinRoomReadyPayload>({
action: Action.JoinRoomReady,
roomId: roomId!,
roomId,
metricsTrigger: payload.metricsTrigger,
});
} catch (err) {
logger.error("Error thrown while handling joinRoom", err);
this.dis?.dispatch<JoinRoomErrorPayload>({
this.dis.dispatch<JoinRoomErrorPayload>({
action: Action.JoinRoomError,
roomId,
err: err instanceof Error ? err : new UserFriendlyError("room|error_join_unknown", { cause: err }),
@@ -583,7 +599,7 @@ export class RoomViewStore extends EventEmitter {
});
if (payload.canAskToJoin && err instanceof MatrixError && err.httpStatus === 403) {
this.dis?.dispatch({ action: Action.PromptAskToJoin });
this.dis.dispatch({ action: Action.PromptAskToJoin });
}
}
}
@@ -665,7 +681,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) {