2017-05-24 18:56:13 +03:00
/ *
Copyright 2017 Vector Creations Ltd
2018-07-03 13:16:44 +03:00
Copyright 2017 , 2018 New Vector Ltd
2022-03-23 08:26:30 +03:00
Copyright 2019 - 2022 The Matrix . org Foundation C . I . C .
2017-05-24 18:56:13 +03:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2020-07-21 12:14:12 +03:00
2021-12-14 18:34:54 +03:00
import React , { ReactNode } from "react" ;
2022-10-19 15:07:03 +03:00
import * as utils from "matrix-js-sdk/src/utils" ;
2023-08-09 18:10:54 +03:00
import { MatrixError , JoinRule , Room , MatrixEvent } from "matrix-js-sdk/src/matrix" ;
2021-10-23 01:23:32 +03:00
import { logger } from "matrix-js-sdk/src/logger" ;
2022-06-07 22:08:36 +03:00
import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom" ;
import { JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom" ;
2022-05-11 18:44:02 +03:00
import { Optional } from "matrix-events-sdk" ;
2022-09-20 18:32:39 +03:00
import EventEmitter from "events" ;
2020-07-21 12:14:12 +03:00
2022-10-19 15:07:03 +03:00
import { MatrixDispatcher } from "../dispatcher/dispatcher" ;
2021-05-24 16:34:06 +03:00
import { MatrixClientPeg } from "../MatrixClientPeg" ;
2017-06-02 13:53:10 +03:00
import Modal from "../Modal" ;
import { _t } from "../languageHandler" ;
2019-11-12 14:43:18 +03:00
import { getCachedRoomIDForAlias , storeRoomAliasInCache } from "../RoomAliasCache" ;
2021-05-24 16:34:06 +03:00
import { Action } from "../dispatcher/actions" ;
import { retry } from "../utils/promise" ;
2021-10-19 18:05:34 +03:00
import { TimelineRenderingType } from "../contexts/RoomContext" ;
2022-02-10 17:29:55 +03:00
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload" ;
2022-02-14 22:31:13 +03:00
import DMRoomMap from "../utils/DMRoomMap" ;
import { isMetaSpace , MetaSpace } from "./spaces" ;
2022-02-17 21:03:27 +03:00
import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload" ;
import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload" ;
2022-02-22 13:04:27 +03:00
import { JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload" ;
import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload" ;
2022-03-03 02:33:40 +03:00
import ErrorDialog from "../components/views/dialogs/ErrorDialog" ;
2022-03-25 00:50:04 +03:00
import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload" ;
2022-09-07 18:42:39 +03:00
import SettingsStore from "../settings/SettingsStore" ;
2022-08-16 14:26:25 +03:00
import { awaitRoomDownSync } from "../utils/RoomUpgrade" ;
2022-09-20 18:32:39 +03:00
import { UPDATE_EVENT } from "./AsyncStore" ;
2022-10-19 15:07:03 +03:00
import { SdkContextClass } from "../contexts/SDKContext" ;
2022-09-27 14:54:51 +03:00
import { CallStore } from "./CallStore" ;
2022-10-25 19:53:31 +03:00
import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload" ;
2022-11-24 11:08:41 +03:00
import {
doClearCurrentVoiceBroadcastPlaybackIfStopped ,
doMaybeSetCurrentVoiceBroadcastPlayback ,
2023-01-02 14:05:51 +03:00
VoiceBroadcastRecording ,
VoiceBroadcastRecordingsStoreEvent ,
2022-11-24 11:08:41 +03:00
} from "../voice-broadcast" ;
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators" ;
2022-12-19 11:44:19 +03:00
import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog" ;
2022-12-28 11:29:42 +03:00
import { pauseNonLiveBroadcastFromOtherRoom } from "../voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom" ;
2023-02-13 14:39:16 +03:00
import { ActionPayload } from "../dispatcher/payloads" ;
2023-08-07 09:27:09 +03:00
import { CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload" ;
import { SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload" ;
2021-09-21 18:48:09 +03:00
2020-09-14 17:16:29 +03:00
const NUM_JOIN_RETRY = 5 ;
2017-05-24 18:56:13 +03:00
2022-09-25 17:57:25 +03:00
interface State {
/ * *
* Whether we ' re joining the currently viewed ( see isJoining ( ) )
* /
joining : boolean ;
/ * *
* Any error that has occurred during joining
* /
joinError : Error | null ;
/ * *
* The ID of the room currently being viewed
* /
roomId : string | null ;
2022-10-25 19:53:31 +03:00
/ * *
* The ID of the thread currently being viewed
* /
threadId : string | null ;
2022-09-25 17:57:25 +03:00
/ * *
* The ID of the room being subscribed to ( in Sliding Sync )
* /
subscribingRoomId : string | null ;
/ * *
* The event to scroll to when the room is first viewed
* /
initialEventId : string | null ;
initialEventPixelOffset : number | null ;
/ * *
* Whether to highlight the initial event
* /
isInitialEventHighlighted : boolean ;
/ * *
* Whether to scroll the initial event into view
* /
initialEventScrollIntoView : boolean ;
/ * *
* The alias of the room ( or null if not originally specified in view_room )
* /
roomAlias : string | null ;
/ * *
* Whether the current room is loading
* /
roomLoading : boolean ;
/ * *
* Any error that has occurred during loading
* /
roomLoadError : MatrixError | null ;
replyingToEvent : MatrixEvent | null ;
shouldPeek : boolean ;
viaServers : string [ ] ;
wasContextSwitch : boolean ;
/ * *
* Whether we ' re viewing a call or call lobby in this room
* /
viewingCall : boolean ;
2023-08-07 09:27:09 +03:00
promptAskToJoin : boolean ;
knocked : boolean ;
2022-09-25 17:57:25 +03:00
}
2017-06-08 17:47:41 +03:00
2022-09-25 17:57:25 +03:00
const INITIAL_STATE : State = {
joining : false ,
joinError : null ,
roomId : null ,
2022-10-25 19:53:31 +03:00
threadId : null ,
2022-09-25 17:57:25 +03:00
subscribingRoomId : null ,
initialEventId : null ,
initialEventPixelOffset : null ,
2017-06-08 17:47:41 +03:00
isInitialEventHighlighted : false ,
2022-04-08 21:48:57 +03:00
initialEventScrollIntoView : true ,
2022-09-25 17:57:25 +03:00
roomAlias : null ,
2017-05-24 18:56:13 +03:00
roomLoading : false ,
2022-09-25 17:57:25 +03:00
roomLoadError : null ,
replyingToEvent : null ,
2020-07-21 12:14:12 +03:00
shouldPeek : false ,
2022-09-25 17:57:25 +03:00
viaServers : [ ] ,
2021-04-29 11:37:21 +03:00
wasContextSwitch : false ,
2022-09-25 17:57:25 +03:00
viewingCall : false ,
2023-08-07 09:27:09 +03:00
promptAskToJoin : false ,
knocked : false ,
2017-05-24 18:56:13 +03:00
} ;
2022-03-23 08:29:02 +03:00
type Listener = ( isActive : boolean ) = > void ;
2017-05-24 18:56:13 +03:00
/ * *
2022-09-20 18:32:39 +03:00
* A class for storing application state for RoomView .
2017-05-24 18:56:13 +03:00
* /
2022-09-20 18:32:39 +03:00
export class RoomViewStore extends EventEmitter {
2022-10-19 15:07:03 +03:00
// initialize state as a copy of the initial state. We need to copy else one RVS can talk to
// another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests.
private state = utils . deepCopy ( INITIAL_STATE ) ;
2020-07-21 12:14:12 +03:00
2023-05-10 10:41:55 +03:00
private dis? : MatrixDispatcher ;
private dispatchToken? : string ;
2022-03-23 08:29:02 +03:00
2022-10-19 15:07:03 +03:00
public constructor ( dis : MatrixDispatcher , private readonly stores : SdkContextClass ) {
2022-09-20 18:32:39 +03:00
super ( ) ;
this . resetDispatcher ( dis ) ;
2023-01-02 14:05:51 +03:00
this . stores . voiceBroadcastRecordingsStore . addListener (
VoiceBroadcastRecordingsStoreEvent . CurrentChanged ,
this . onCurrentBroadcastRecordingChanged ,
) ;
2017-05-24 18:56:13 +03:00
}
2022-05-11 18:44:02 +03:00
public addRoomListener ( roomId : string , fn : Listener ) : void {
2022-09-20 18:32:39 +03:00
this . on ( roomId , fn ) ;
2022-03-23 08:29:02 +03:00
}
2022-05-11 18:44:02 +03:00
public removeRoomListener ( roomId : string , fn : Listener ) : void {
2022-09-20 18:32:39 +03:00
this . off ( roomId , fn ) ;
2022-03-23 08:29:02 +03:00
}
2022-05-11 18:44:02 +03:00
private emitForRoom ( roomId : string , isActive : boolean ) : void {
2022-09-20 18:32:39 +03:00
this . emit ( roomId , isActive ) ;
2022-03-23 08:29:02 +03:00
}
2023-01-12 16:25:14 +03:00
private onCurrentBroadcastRecordingChanged = ( recording : VoiceBroadcastRecording | null ) : void = > {
2023-01-02 14:05:51 +03:00
if ( recording === null ) {
const room = this . stores . client ? . getRoom ( this . state . roomId || undefined ) ;
if ( room ) {
this . doMaybeSetCurrentVoiceBroadcastPlayback ( room ) ;
}
}
} ;
2022-09-25 17:57:25 +03:00
private setState ( newState : Partial < State > ) : void {
2020-02-06 20:57:17 +03:00
// If values haven't changed, there's nothing to do.
// This only tries a shallow comparison, so unchanged objects will slip
// through, but that's probably okay for now.
let stateChanged = false ;
for ( const key of Object . keys ( newState ) ) {
2023-01-02 14:05:51 +03:00
if ( this . state [ key as keyof State ] !== newState [ key as keyof State ] ) {
2020-02-06 20:57:17 +03:00
stateChanged = true ;
break ;
}
}
if ( ! stateChanged ) {
return ;
}
2022-12-19 11:44:19 +03:00
if ( newState . viewingCall ) {
// Pause current broadcast, if any
this . stores . voiceBroadcastPlaybacksStore . getCurrent ( ) ? . pause ( ) ;
if ( this . stores . voiceBroadcastRecordingsStore . getCurrent ( ) ) {
showCantStartACallDialog ( ) ;
newState . viewingCall = false ;
}
}
2022-03-23 08:29:02 +03:00
const lastRoomId = this . state . roomId ;
2020-07-21 12:14:12 +03:00
this . state = Object . assign ( this . state , newState ) ;
2022-03-23 08:29:02 +03:00
if ( lastRoomId !== this . state . roomId ) {
if ( lastRoomId ) this . emitForRoom ( lastRoomId , false ) ;
if ( this . state . roomId ) this . emitForRoom ( this . state . roomId , true ) ;
2022-03-25 00:50:04 +03:00
// 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.
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch < ActiveRoomChangedPayload > ( {
2022-03-25 00:50:04 +03:00
action : Action.ActiveRoomChanged ,
oldRoomId : lastRoomId ,
newRoomId : this.state.roomId ,
2022-04-01 04:38:00 +03:00
} ) ;
2022-03-23 08:29:02 +03:00
}
2022-09-20 18:32:39 +03:00
this . emit ( UPDATE_EVENT ) ;
2017-05-24 18:56:13 +03:00
}
2022-11-24 11:08:41 +03:00
private doMaybeSetCurrentVoiceBroadcastPlayback ( room : Room ) : void {
2023-04-25 11:28:48 +03:00
if ( ! this . stores . client ) return ;
2022-11-24 11:08:41 +03:00
doMaybeSetCurrentVoiceBroadcastPlayback (
room ,
this . stores . client ,
this . stores . voiceBroadcastPlaybacksStore ,
this . stores . voiceBroadcastRecordingsStore ,
) ;
}
private onRoomStateEvents ( event : MatrixEvent ) : void {
const roomId = event . getRoomId ? . ( ) ;
// no room or not current room
if ( ! roomId || roomId !== this . state . roomId ) return ;
const room = this . stores . client ? . getRoom ( roomId ) ;
if ( room ) {
this . doMaybeSetCurrentVoiceBroadcastPlayback ( room ) ;
}
}
2023-02-13 14:39:16 +03:00
private onDispatch ( payload : ActionPayload ) : void {
2022-09-20 18:32:39 +03:00
// eslint-disable-line @typescript-eslint/naming-convention
2017-05-24 18:56:13 +03:00
switch ( payload . action ) {
// view_room:
2017-06-08 16:17:49 +03:00
// - room_alias: '#somealias:matrix.org'
// - room_id: '!roomid123:matrix.org'
// - event_id: '$213456782:matrix.org'
// - event_offset: 100
// - highlighted: true
2021-11-25 23:49:43 +03:00
case Action . ViewRoom :
2023-02-13 14:39:16 +03:00
this . viewRoom ( payload as ViewRoomPayload ) ;
2017-05-24 18:56:13 +03:00
break ;
2022-10-25 19:53:31 +03:00
case Action . ViewThread :
2023-02-13 14:39:16 +03:00
this . viewThread ( payload as ThreadPayload ) ;
2022-10-25 19:53:31 +03:00
break ;
2020-07-21 12:22:03 +03:00
// for these events blank out the roomId as we are no longer in the RoomView
2020-07-21 12:20:30 +03:00
case "view_welcome_page" :
2022-02-22 13:04:27 +03:00
case Action . ViewHomePage :
2020-07-21 12:14:12 +03:00
this . setState ( {
2017-10-24 18:32:52 +03:00
roomId : null ,
roomAlias : null ,
2021-04-28 11:04:02 +03:00
viaServers : [ ] ,
2021-04-29 11:37:21 +03:00
wasContextSwitch : false ,
2022-09-25 17:57:25 +03:00
viewingCall : false ,
2017-10-24 18:32:52 +03:00
} ) ;
2022-11-24 11:08:41 +03:00
doClearCurrentVoiceBroadcastPlaybackIfStopped ( this . stores . voiceBroadcastPlaybacksStore ) ;
break ;
case "MatrixActions.RoomState.events" :
this . onRoomStateEvents ( ( payload as IRoomStateEventsActionPayload ) . event ) ;
2017-10-24 18:32:52 +03:00
break ;
2022-02-22 13:04:27 +03:00
case Action . ViewRoomError :
2023-02-13 14:39:16 +03:00
this . viewRoomError ( payload as ViewRoomErrorPayload ) ;
2017-06-01 20:01:30 +03:00
break ;
2017-05-25 19:04:42 +03:00
case "will_join" :
2020-07-21 12:14:12 +03:00
this . setState ( {
2017-05-25 19:04:42 +03:00
joining : true ,
} ) ;
break ;
case "cancel_join" :
2020-07-21 12:14:12 +03:00
this . setState ( {
2017-05-25 19:04:42 +03:00
joining : false ,
} ) ;
break ;
2017-05-24 18:56:13 +03:00
// join_room:
// - opts: options for joinRoom
2021-05-24 16:34:06 +03:00
case Action . JoinRoom :
2023-02-13 14:39:16 +03:00
this . joinRoom ( payload as JoinRoomPayload ) ;
2017-05-24 18:56:13 +03:00
break ;
2021-05-24 16:34:06 +03:00
case Action . JoinRoomError :
2023-02-13 14:39:16 +03:00
this . joinRoomError ( payload as JoinRoomErrorPayload ) ;
2017-06-02 13:53:10 +03:00
break ;
2022-02-17 21:03:27 +03:00
case Action . JoinRoomReady : {
2022-01-07 12:23:54 +03:00
if ( this . state . roomId === payload . roomId ) {
this . setState ( { shouldPeek : false } ) ;
}
2022-02-17 21:03:27 +03:00
2023-06-15 17:11:49 +03:00
awaitRoomDownSync ( MatrixClientPeg . safeGet ( ) , payload . roomId ) . then ( ( room ) = > {
2022-02-17 21:03:27 +03:00
const numMembers = room . getJoinedMemberCount ( ) ;
const roomSize =
numMembers > 1000
? "MoreThanAThousand"
: numMembers > 100
? "OneHundredAndOneToAThousand"
: numMembers > 10
? "ElevenToOneHundred"
: numMembers > 2
? "ThreeToTen"
: numMembers > 1
? "Two"
: "One" ;
2022-10-19 15:07:03 +03:00
this . stores . posthogAnalytics . trackEvent < JoinedRoomEvent > ( {
2022-02-17 21:03:27 +03:00
eventName : "JoinedRoom" ,
trigger : payload.metricsTrigger ,
roomSize ,
isDM : ! ! DMRoomMap . shared ( ) . getUserIdForRoomId ( room . roomId ) ,
isSpace : room.isSpaceRoom ( ) ,
} ) ;
2022-08-16 14:26:25 +03:00
} ) ;
2022-02-17 21:03:27 +03:00
2020-03-31 12:37:56 +03:00
break ;
2022-02-17 21:03:27 +03:00
}
2019-07-04 01:46:37 +03:00
case "on_client_not_viable" :
2022-05-26 11:56:53 +03:00
case Action . OnLoggedOut :
2017-05-25 19:16:16 +03:00
this . reset ( ) ;
break ;
2018-02-20 02:41:07 +03:00
case "reply_to_event" :
2023-01-12 17:52:52 +03:00
// Thread timeline view handles its own reply-to-state
if ( TimelineRenderingType . Thread !== payload . context ) {
// If currently viewed room does not match the room in which we wish to reply then change rooms this
// can happen when performing a search across all rooms. Persist the data from this event for both
// room and search timeline rendering types, search will get auto-closed by RoomView at this time.
2022-03-11 20:21:28 +03:00
if ( payload . event && payload . event . getRoomId ( ) !== this . state . roomId ) {
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch < ViewRoomPayload > ( {
2021-11-25 23:49:43 +03:00
action : Action.ViewRoom ,
2021-10-19 18:05:34 +03:00
room_id : payload.event.getRoomId ( ) ,
replyingToEvent : payload.event ,
2022-02-17 21:03:27 +03:00
metricsTrigger : undefined , // room doesn't change
2021-10-19 18:05:34 +03:00
} ) ;
} else {
this . setState ( {
replyingToEvent : payload.event ,
} ) ;
}
2019-09-09 11:34:08 +03:00
}
2018-02-20 02:41:07 +03:00
break ;
2023-08-07 09:27:09 +03:00
case Action . PromptAskToJoin : {
this . setState ( { promptAskToJoin : true } ) ;
break ;
}
case Action . SubmitAskToJoin : {
this . submitAskToJoin ( payload as SubmitAskToJoinPayload ) ;
break ;
}
case Action . CancelAskToJoin : {
this . cancelAskToJoin ( payload as CancelAskToJoinPayload ) ;
break ;
}
2017-05-24 18:56:13 +03:00
}
}
2022-02-10 17:29:55 +03:00
private async viewRoom ( payload : ViewRoomPayload ) : Promise < void > {
2017-06-01 20:01:30 +03:00
if ( payload . room_id ) {
2023-06-15 17:11:49 +03:00
const room = MatrixClientPeg . safeGet ( ) . getRoom ( payload . room_id ) ;
2022-09-27 14:54:51 +03:00
2022-02-17 21:03:27 +03:00
if ( payload . metricsTrigger !== null && payload . room_id !== this . state . roomId ) {
2022-02-14 22:31:13 +03:00
let activeSpace : ViewRoomEvent [ "activeSpace" ] ;
2022-10-19 15:07:03 +03:00
if ( this . stores . spaceStore . activeSpace === MetaSpace . Home ) {
2022-02-14 22:31:13 +03:00
activeSpace = "Home" ;
2022-10-19 15:07:03 +03:00
} else if ( isMetaSpace ( this . stores . spaceStore . activeSpace ) ) {
2022-02-14 22:31:13 +03:00
activeSpace = "Meta" ;
} else {
2022-10-19 15:07:03 +03:00
activeSpace =
this . stores . spaceStore . activeSpaceRoom ? . getJoinRule ( ) === JoinRule . Public
2022-02-14 22:31:13 +03:00
? "Public"
: "Private" ;
}
2022-10-19 15:07:03 +03:00
this . stores . posthogAnalytics . trackEvent < ViewRoomEvent > ( {
2022-02-10 17:29:55 +03:00
eventName : "ViewRoom" ,
2022-02-17 21:03:27 +03:00
trigger : payload.metricsTrigger ,
viaKeyboard : payload.metricsViaKeyboard ,
2022-02-14 22:31:13 +03:00
isDM : ! ! DMRoomMap . shared ( ) . getUserIdForRoomId ( payload . room_id ) ,
2022-09-27 14:54:51 +03:00
isSpace : room?.isSpaceRoom ( ) ,
2022-02-14 22:31:13 +03:00
activeSpace ,
2022-02-10 17:29:55 +03:00
} ) ;
}
2022-09-27 14:54:51 +03:00
2022-09-07 18:42:39 +03:00
if ( SettingsStore . getValue ( "feature_sliding_sync" ) && this . state . roomId !== payload . room_id ) {
2022-09-20 18:32:39 +03:00
if ( this . state . subscribingRoomId && this . state . subscribingRoomId !== payload . room_id ) {
2022-09-07 18:42:39 +03:00
// unsubscribe from this room, but don't await it as we don't care when this gets done.
2022-10-19 15:07:03 +03:00
this . stores . slidingSyncManager . setRoomVisible ( this . state . subscribingRoomId , false ) ;
2022-09-07 18:42:39 +03:00
}
this . setState ( {
2022-09-12 13:55:46 +03:00
subscribingRoomId : payload.room_id ,
2022-09-07 18:42:39 +03:00
roomId : payload.room_id ,
initialEventId : null ,
initialEventPixelOffset : null ,
initialEventScrollIntoView : true ,
roomAlias : null ,
roomLoading : true ,
roomLoadError : null ,
viaServers : payload.via_servers ,
wasContextSwitch : payload.context_switch ,
2022-09-25 17:57:25 +03:00
viewingCall : payload.view_call ? ? false ,
2022-09-07 18:42:39 +03:00
} ) ;
// set this room as the room subscription. We need to await for it as this will fetch
// all room state for this room, which is required before we get the state below.
2022-10-19 15:07:03 +03:00
await this . stores . slidingSyncManager . setRoomVisible ( payload . room_id , true ) ;
2022-09-12 13:55:46 +03:00
// Whilst we were subscribing another room was viewed, so stop what we're doing and
// unsubscribe
if ( this . state . subscribingRoomId !== payload . room_id ) {
2022-10-19 15:07:03 +03:00
this . stores . slidingSyncManager . setRoomVisible ( payload . room_id , false ) ;
2022-09-12 13:55:46 +03:00
return ;
}
2022-09-07 18:42:39 +03:00
// Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch ( {
2022-09-07 18:42:39 +03:00
. . . payload ,
} ) ;
return ;
}
2022-02-10 17:29:55 +03:00
2022-09-25 17:57:25 +03:00
const newState : Partial < State > = {
2017-06-01 20:01:30 +03:00
roomId : payload.room_id ,
2022-09-25 17:57:25 +03:00
roomAlias : payload.room_alias ? ? null ,
initialEventId : payload.event_id ? ? null ,
isInitialEventHighlighted : payload.highlighted ? ? false ,
2022-04-08 21:48:57 +03:00
initialEventScrollIntoView : payload.scroll_into_view ? ? true ,
2017-06-02 11:22:48 +03:00
roomLoading : false ,
roomLoadError : null ,
2017-06-16 20:24:07 +03:00
// should peek by default
shouldPeek : payload.should_peek === undefined ? true : payload . should_peek ,
2017-09-15 00:22:21 +03:00
// have we sent a join request for this room and are waiting for a response?
joining : payload.joining || false ,
2018-02-20 02:41:07 +03:00
// Reset replyingToEvent because we don't want cross-room because bad UX
replyingToEvent : null ,
2022-09-25 17:57:25 +03:00
viaServers : payload.via_servers ? ? [ ] ,
wasContextSwitch : payload.context_switch ? ? false ,
viewingCall :
payload . view_call ? ?
2022-09-27 14:54:51 +03:00
( payload . room_id === this . state . roomId
? this . state . viewingCall
2022-10-07 05:27:28 +03:00
: CallStore . instance . getActiveCall ( payload . room_id ) !== null ) ,
2017-06-08 17:54:23 +03:00
} ;
2019-01-17 12:29:37 +03:00
2019-09-09 11:34:08 +03:00
// Allow being given an event to be replied to when switching rooms but sanity check its for this room
2022-02-10 17:29:55 +03:00
if ( payload . replyingToEvent ? . getRoomId ( ) === payload . room_id ) {
2019-09-09 11:34:08 +03:00
newState . replyingToEvent = payload . replyingToEvent ;
2022-09-15 15:09:16 +03:00
} else if ( this . state . replyingToEvent ? . getRoomId ( ) === payload . room_id ) {
// if the reply-to matches the desired room, e.g visiting a permalink then maintain replyingToEvent
// See https://github.com/vector-im/element-web/issues/21462
2022-03-23 16:38:21 +03:00
newState . replyingToEvent = this . state . replyingToEvent ;
2019-09-09 11:34:08 +03:00
}
2020-07-21 12:14:12 +03:00
this . setState ( newState ) ;
2019-01-17 12:29:37 +03:00
2017-09-15 01:06:00 +03:00
if ( payload . auto_join ) {
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch < JoinRoomPayload > ( {
2021-05-24 16:34:06 +03:00
. . . payload ,
action : Action.JoinRoom ,
roomId : payload.room_id ,
2022-02-17 21:03:27 +03:00
metricsTrigger : payload.metricsTrigger as JoinRoomPayload [ "metricsTrigger" ] ,
2021-05-24 16:34:06 +03:00
} ) ;
2017-09-15 01:06:00 +03:00
}
2022-11-24 11:08:41 +03:00
if ( room ) {
2022-12-28 11:29:42 +03:00
pauseNonLiveBroadcastFromOtherRoom ( room , this . stores . voiceBroadcastPlaybacksStore ) ;
2022-11-24 11:08:41 +03:00
this . doMaybeSetCurrentVoiceBroadcastPlayback ( room ) ;
}
2017-06-02 11:22:48 +03:00
} else if ( payload . room_alias ) {
2019-11-12 14:43:18 +03:00
// Try the room alias to room ID navigation cache first to avoid
// blocking room navigation on the homeserver.
2019-11-12 16:29:01 +03:00
let roomId = getCachedRoomIDForAlias ( payload . room_alias ) ;
if ( ! roomId ) {
// Room alias cache miss, so let's ask the homeserver. Resolve the alias
// and then do a second dispatch with the room ID acquired.
2020-07-21 12:14:12 +03:00
this . setState ( {
2019-11-12 16:29:01 +03:00
roomId : null ,
initialEventId : null ,
initialEventPixelOffset : null ,
2022-09-25 17:57:25 +03:00
isInitialEventHighlighted : false ,
2022-04-08 21:48:57 +03:00
initialEventScrollIntoView : true ,
2019-11-12 16:29:01 +03:00
roomAlias : payload.room_alias ,
roomLoading : true ,
roomLoadError : null ,
2021-04-28 11:04:02 +03:00
viaServers : payload.via_servers ,
2021-04-29 11:37:21 +03:00
wasContextSwitch : payload.context_switch ,
2022-09-25 17:57:25 +03:00
viewingCall : payload.view_call ? ? false ,
2019-11-12 14:43:18 +03:00
} ) ;
2019-11-12 16:29:01 +03:00
try {
2023-06-15 17:11:49 +03:00
const result = await MatrixClientPeg . safeGet ( ) . getRoomIdForAlias ( payload . room_alias ) ;
2019-11-12 16:29:01 +03:00
storeRoomAliasInCache ( payload . room_alias , result . room_id ) ;
roomId = result . room_id ;
} catch ( err ) {
2021-10-15 17:30:53 +03:00
logger . error ( "RVS failed to get room id for alias: " , err ) ;
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch < ViewRoomErrorPayload > ( {
2022-02-22 13:04:27 +03:00
action : Action.ViewRoomError ,
2019-11-12 16:29:01 +03:00
room_id : null ,
room_alias : payload.room_alias ,
2023-07-07 16:46:12 +03:00
err : err instanceof MatrixError ? err : undefined ,
2019-11-12 16:29:01 +03:00
} ) ;
return ;
}
2019-11-12 14:43:18 +03:00
}
2019-11-12 16:29:01 +03:00
2022-01-12 23:12:28 +03:00
// Re-fire the payload with the newly found room_id
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch ( {
2022-01-12 23:12:28 +03:00
. . . payload ,
2019-11-12 16:29:01 +03:00
room_id : roomId ,
2017-05-24 18:56:13 +03:00
} ) ;
}
}
2022-10-25 19:53:31 +03:00
private viewThread ( payload : ThreadPayload ) : void {
this . setState ( {
threadId : payload.thread_id ,
} ) ;
}
2022-05-11 18:44:02 +03:00
private viewRoomError ( payload : ViewRoomErrorPayload ) : void {
2020-07-21 12:14:12 +03:00
this . setState ( {
2017-06-01 20:01:30 +03:00
roomId : payload.room_id ,
roomAlias : payload.room_alias ,
roomLoading : false ,
roomLoadError : payload.err ,
} ) ;
}
2022-05-11 18:44:02 +03:00
private async joinRoom ( payload : JoinRoomPayload ) : Promise < void > {
2020-07-21 12:14:12 +03:00
this . setState ( {
2017-05-24 18:56:13 +03:00
joining : true ,
} ) ;
2020-09-14 17:16:29 +03:00
2022-01-07 12:23:54 +03:00
// take a copy of roomAlias & roomId as they may change by the time the join is complete
2023-04-25 11:28:48 +03:00
const { roomAlias , roomId = payload . roomId } = this . state ;
const address = roomAlias || roomId ! ;
2021-04-28 11:07:02 +03:00
const viaServers = this . state . viaServers || [ ] ;
2020-09-14 17:16:29 +03:00
try {
2023-06-15 17:11:49 +03:00
const cli = MatrixClientPeg . safeGet ( ) ;
2022-02-17 21:03:27 +03:00
await retry < Room , MatrixError > (
( ) = >
cli . joinRoom ( address , {
2021-04-28 11:04:02 +03:00
viaServers ,
2022-02-17 21:03:27 +03:00
. . . ( payload . opts || { } ) ,
2021-03-24 19:45:53 +03:00
} ) ,
NUM_JOIN_RETRY ,
( err ) = > {
2023-07-19 13:49:52 +03:00
// if we received a Gateway timeout or Cloudflare timeout then retry
return err . httpStatus === 504 || err . httpStatus === 524 ;
2020-09-14 17:16:29 +03:00
} ,
) ;
2022-02-10 17:29:55 +03:00
2020-03-31 12:37:56 +03:00
// 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.
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch < JoinRoomReadyPayload > ( {
2021-05-24 16:34:06 +03:00
action : Action.JoinRoomReady ,
2023-04-25 11:28:48 +03:00
roomId : roomId ! ,
2022-02-17 21:03:27 +03:00
metricsTrigger : payload.metricsTrigger ,
2021-05-24 16:34:06 +03:00
} ) ;
2020-09-14 17:16:29 +03:00
} catch ( err ) {
2023-05-10 10:41:55 +03:00
this . dis ? . dispatch ( {
2021-05-24 16:34:06 +03:00
action : Action.JoinRoomError ,
2022-01-07 12:23:54 +03:00
roomId ,
2022-05-10 20:09:31 +03:00
err ,
2023-08-07 09:27:09 +03:00
canAskToJoin : payload.canAskToJoin ,
2017-06-02 13:53:10 +03:00
} ) ;
2023-08-07 09:27:09 +03:00
if ( payload . canAskToJoin ) {
this . dis ? . dispatch ( { action : Action.PromptAskToJoin } ) ;
}
2020-09-14 17:16:29 +03:00
}
2017-06-02 13:53:10 +03:00
}
2023-03-16 14:07:29 +03:00
private getInvitingUserId ( roomId : string ) : string | undefined {
2023-06-15 17:11:49 +03:00
const cli = MatrixClientPeg . safeGet ( ) ;
2020-07-29 17:51:37 +03:00
const room = cli . getRoom ( roomId ) ;
2022-05-11 18:44:02 +03:00
if ( room ? . getMyMembership ( ) === "invite" ) {
2023-03-16 14:07:29 +03:00
const myMember = room . getMember ( cli . getSafeUserId ( ) ) ;
2020-07-29 17:51:37 +03:00
const inviteEvent = myMember ? myMember.events.member : null ;
2023-03-16 14:07:29 +03:00
return inviteEvent ? . getSender ( ) ;
2020-07-29 17:51:37 +03:00
}
}
2022-05-11 18:44:02 +03:00
public showJoinRoomError ( err : MatrixError , roomId : string ) : void {
2022-03-29 17:02:12 +03:00
let description : ReactNode = err . message ? err.message : JSON.stringify ( err ) ;
logger . log ( "Failed to join room:" , description ) ;
2021-05-24 16:34:06 +03:00
if ( err . name === "ConnectionError" ) {
2022-03-29 17:02:12 +03:00
description = _t ( "There was an error joining." ) ;
2021-05-24 16:34:06 +03:00
} else if ( err . errcode === "M_INCOMPATIBLE_ROOM_VERSION" ) {
2022-03-29 17:02:12 +03:00
description = (
< div >
{ _t ( "Sorry, your homeserver is too old to participate here." ) }
< br / >
2021-07-20 00:43:11 +03:00
{ _t ( "Please contact your homeserver administrator." ) }
2021-05-24 16:34:06 +03:00
< / div >
) ;
} else if ( err . httpStatus === 404 ) {
2022-03-23 08:26:30 +03:00
const invitingUserId = this . getInvitingUserId ( roomId ) ;
2023-02-24 18:59:30 +03:00
// provide a better error message for invites
2021-05-24 16:34:06 +03:00
if ( invitingUserId ) {
// if the inviting user is on the same HS, there can only be one cause: they left.
2023-06-15 17:11:49 +03:00
if ( invitingUserId . endsWith ( ` : ${ MatrixClientPeg . safeGet ( ) . getDomain ( ) } ` ) ) {
2022-03-29 17:02:12 +03:00
description = _t ( "The person who invited you has already left." ) ;
2021-05-24 16:34:06 +03:00
} else {
2022-03-29 17:02:12 +03:00
description = _t ( "The person who invited you has already left, or their server is offline." ) ;
2021-05-24 16:34:06 +03:00
}
}
2023-02-24 18:59:30 +03:00
// provide a more detailed error than "No known servers" when attempting to
// join using a room ID and no via servers
if ( roomId === this . state . roomId && this . state . viaServers . length === 0 ) {
description = (
< div >
{ _t (
2023-08-22 18:32:05 +03:00
"You attempted to join using a room ID without providing a list of servers to join through. Room IDs are internal identifiers and cannot be used to join a room without additional information." ,
2023-02-24 18:59:30 +03:00
) }
< br / >
< br / >
{ _t ( "If you know a room address, try joining through that instead." ) }
< / div >
) ;
}
2021-05-24 16:34:06 +03:00
}
2022-06-14 19:51:51 +03:00
Modal . createDialog ( ErrorDialog , {
2022-03-29 17:02:12 +03:00
title : _t ( "Failed to join" ) ,
description ,
2021-05-24 16:34:06 +03:00
} ) ;
2017-05-24 18:56:13 +03:00
}
2022-05-11 18:44:02 +03:00
private joinRoomError ( payload : JoinRoomErrorPayload ) : void {
2021-10-27 17:24:31 +03:00
this . setState ( {
joining : false ,
joinError : payload.err ,
} ) ;
2023-08-07 09:27:09 +03:00
if ( payload . err && ! payload . canAskToJoin ) {
2023-02-24 18:59:30 +03:00
this . showJoinRoomError ( payload . err , payload . roomId ) ;
}
2021-10-27 17:24:31 +03:00
}
2022-05-11 18:44:02 +03:00
public reset ( ) : void {
2020-07-21 12:14:12 +03:00
this . state = Object . assign ( { } , INITIAL_STATE ) ;
2017-05-24 18:56:13 +03:00
}
2022-09-20 18:32:39 +03:00
/ * *
* Reset which dispatcher should be used to listen for actions . The old dispatcher will be
* unregistered .
* @param dis The new dispatcher to use .
* /
2023-01-12 16:25:14 +03:00
public resetDispatcher ( dis : MatrixDispatcher ) : void {
2022-09-20 18:32:39 +03:00
if ( this . dispatchToken ) {
2023-05-10 10:41:55 +03:00
this . dis ? . unregister ( this . dispatchToken ) ;
2022-09-20 18:32:39 +03:00
}
this . dis = dis ;
if ( dis ) {
// Some tests mock the dispatcher file resulting in an empty defaultDispatcher
// so rather than dying here, just ignore it. When we no longer mock files like this,
// we should remove the null check.
this . dispatchToken = this . dis . register ( this . onDispatch . bind ( this ) ) ;
}
}
2017-06-08 18:00:12 +03:00
// The room ID of the room currently being viewed
2022-05-11 18:44:02 +03:00
public getRoomId ( ) : Optional < string > {
2020-07-21 12:14:12 +03:00
return this . state . roomId ;
2017-05-24 18:56:13 +03:00
}
2022-10-25 19:53:31 +03:00
public getThreadId ( ) : Optional < string > {
return this . state . threadId ;
}
2017-06-08 19:28:56 +03:00
// The event to scroll to when the room is first viewed
2022-05-11 18:44:02 +03:00
public getInitialEventId ( ) : Optional < string > {
2020-07-21 12:14:12 +03:00
return this . state . initialEventId ;
2017-06-08 16:17:49 +03:00
}
2017-06-08 18:00:12 +03:00
// Whether to highlight the initial event
2022-05-11 18:44:02 +03:00
public isInitialEventHighlighted ( ) : boolean {
2020-07-21 12:14:12 +03:00
return this . state . isInitialEventHighlighted ;
2017-06-08 16:17:49 +03:00
}
2022-04-08 21:48:57 +03:00
// Whether to avoid jumping to the initial event
2022-05-11 18:44:02 +03:00
public initialEventScrollIntoView ( ) : boolean {
2022-04-08 21:48:57 +03:00
return this . state . initialEventScrollIntoView ;
}
2017-06-08 18:00:12 +03:00
// The room alias of the room (or null if not originally specified in view_room)
2022-05-11 18:44:02 +03:00
public getRoomAlias ( ) : Optional < string > {
2020-07-21 12:14:12 +03:00
return this . state . roomAlias ;
2017-05-24 18:56:13 +03:00
}
2017-06-08 18:00:12 +03:00
// Whether the current room is loading (true whilst resolving an alias)
2022-05-11 18:44:02 +03:00
public isRoomLoading ( ) : boolean {
2020-07-21 12:14:12 +03:00
return this . state . roomLoading ;
2017-05-24 18:56:13 +03:00
}
2017-06-08 18:00:12 +03:00
// Any error that has occurred during loading
2022-05-11 18:44:02 +03:00
public getRoomLoadError ( ) : Optional < MatrixError > {
2020-07-21 12:14:12 +03:00
return this . state . roomLoadError ;
2017-06-01 20:01:30 +03:00
}
2017-09-15 17:07:09 +03:00
// True if we're expecting the user to be joined to the room currently being
// viewed. Note that this is left true after the join request has finished,
// since we should still consider a join to be in progress until the room
// & member events come down the sync.
//
2022-05-10 01:52:05 +03:00
// This flag remains true after the room has been successfully joined,
2017-09-15 17:07:09 +03:00
// (this store doesn't listen for the appropriate member events)
2017-09-19 12:21:20 +03:00
// so you should always observe the joined state from the member event
// if a room object is present.
2017-09-15 17:07:09 +03:00
// ie. The correct logic is:
2017-09-19 12:21:20 +03:00
// if (room) {
// if (myMember.membership == 'joined') {
// // user is joined to the room
// } else {
// // Not joined
// }
2017-09-15 17:07:09 +03:00
// } else {
2022-10-19 15:07:03 +03:00
// if (this.stores.roomViewStore.isJoining()) {
2017-09-15 17:07:09 +03:00
// // show spinner
// } else {
// // show join prompt
// }
// }
2022-05-11 18:44:02 +03:00
public isJoining ( ) : boolean {
2020-07-21 12:14:12 +03:00
return this . state . joining ;
2017-05-24 18:56:13 +03:00
}
2017-06-08 18:00:12 +03:00
// Any error that has occurred during joining
2022-05-11 18:44:02 +03:00
public getJoinError ( ) : Optional < Error > {
2020-07-21 12:14:12 +03:00
return this . state . joinError ;
2017-05-24 18:56:13 +03:00
}
2017-06-16 18:12:52 +03:00
2017-12-10 15:50:41 +03:00
// The mxEvent if one is currently being replied to/quoted
2023-02-16 20:21:44 +03:00
public getQuotingEvent ( ) : MatrixEvent | null {
2020-07-21 12:14:12 +03:00
return this . state . replyingToEvent ;
2017-12-10 15:50:41 +03:00
}
2022-05-11 18:44:02 +03:00
public shouldPeek ( ) : boolean {
2020-07-21 12:14:12 +03:00
return this . state . shouldPeek ;
2017-06-16 20:24:07 +03:00
}
2021-04-29 11:37:21 +03:00
2022-05-11 18:44:02 +03:00
public getWasContextSwitch ( ) : boolean {
2021-04-29 11:37:21 +03:00
return this . state . wasContextSwitch ;
}
2022-09-25 17:57:25 +03:00
public isViewingCall ( ) : boolean {
return this . state . viewingCall ;
}
2023-08-07 09:27:09 +03:00
/ * *
* Gets the current state of the 'promptForAskToJoin' property .
*
* @returns { boolean } The value of the 'promptForAskToJoin' property .
* /
public promptAskToJoin ( ) : boolean {
return this . state . promptAskToJoin ;
}
/ * *
* Gets the current state of the 'knocked' property .
*
* @returns { boolean } The value of the 'knocked' property .
* /
public knocked ( ) : boolean {
return this . state . knocked ;
}
/ * *
* Submits a request to join a room by sending a knock request .
*
* @param { SubmitAskToJoinPayload } payload - The payload containing information to submit the request .
* @returns { void }
* /
private submitAskToJoin ( payload : SubmitAskToJoinPayload ) : void {
MatrixClientPeg . safeGet ( )
. knockRoom ( payload . roomId , { viaServers : this.state.viaServers , . . . payload . opts } )
. then ( ( ) = > this . setState ( { promptAskToJoin : false , knocked : true } ) )
. catch ( ( err : MatrixError ) = > {
this . setState ( { promptAskToJoin : false } ) ;
Modal . createDialog ( ErrorDialog , {
title : _t ( "Failed to join" ) ,
description : err.httpStatus === 403 ? _t ( "You need an invite to access this room." ) : err . message ,
} ) ;
} ) ;
}
/ * *
* Cancels a request to join a room by sending a leave request .
*
* @param { CancelAskToJoinPayload } payload - The payload containing information to cancel the request .
* @returns { void }
* /
private cancelAskToJoin ( payload : CancelAskToJoinPayload ) : void {
MatrixClientPeg . safeGet ( )
. leave ( payload . roomId )
. then ( ( ) = > this . setState ( { knocked : false } ) )
. catch ( ( err : MatrixError ) = >
Modal . createDialog ( ErrorDialog , { title : _t ( "Failed to cancel" ) , description : err.message } ) ,
) ;
}
2017-05-24 18:56:13 +03:00
}