From 1a1e5a9db825036d0d38090c7705acdfb11ba40c Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 20 Aug 2025 13:30:21 +0200 Subject: [PATCH] Show 'reconnecting' message when sync loop is disconnected With this change I'm also taking care to not show the standard "Connection to the server has been lost" banner in the call view, since that is now covered by the 'reconnecting' message. --- src/App.tsx | 2 -- src/AppBar.tsx | 6 +++++- src/Header.tsx | 25 ++++++++++++++++++------- src/room/InCallView.tsx | 6 +++++- src/state/CallViewModel.ts | 37 +++++++++++++++++++++++++++++-------- src/utils/observable.ts | 9 +++++++++ 6 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6d7d1e1e..b87f587c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,6 @@ import { RegisterPage } from "./auth/RegisterPage"; import { RoomPage } from "./room/RoomPage"; import { ClientProvider } from "./ClientContext"; import { ErrorPage, LoadingPage } from "./FullScreenView"; -import { DisconnectedBanner } from "./DisconnectedBanner"; import { Initializer } from "./initializer"; import { widget } from "./widget"; import { useTheme } from "./useTheme"; @@ -86,7 +85,6 @@ export const App: FC = ({ vm }) => { } > - } /> } /> diff --git a/src/AppBar.tsx b/src/AppBar.tsx index e70bb50d..aaa7565e 100644 --- a/src/AppBar.tsx +++ b/src/AppBar.tsx @@ -61,7 +61,11 @@ export const AppBar: FC = ({ children }) => { style={{ display: hidden ? "none" : "block" }} className={styles.bar} > -
+
diff --git a/src/Header.tsx b/src/Header.tsx index 577410f8..cffc3402 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -17,27 +17,38 @@ import Logo from "./icons/Logo.svg?react"; import { Avatar, Size } from "./Avatar"; import { EncryptionLock } from "./room/EncryptionLock"; import { useMediaQuery } from "./useMediaQuery"; +import { DisconnectedBanner } from "./DisconnectedBanner"; interface HeaderProps extends HTMLAttributes { ref?: Ref; children: ReactNode; className?: string; + /** + * Whether the header should display an informational banner whenever the + * client is disconnected from the homeserver. + * @default true + */ + disconnectedBanner?: boolean; } export const Header: FC = ({ ref, children, className, + disconnectedBanner = true, ...rest }) => { return ( -
- {children} -
+ <> +
+ {children} +
+ {disconnectedBanner && } + ); }; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index f9bd681c..d4026099 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -508,7 +508,11 @@ export const InCallView: FC = ({ break; case "standard": header = ( -
+
this.matrixRTCSession.memberships), ); - private readonly matrixRTCConnected$ = this.scope.behavior( - this.memberships$.pipe( - map((ms) => - ms.some( - (m) => m.sender === this.userId && m.deviceId === this.deviceId, + private readonly matrixConnected$ = this.scope.behavior( + // To consider ourselves connected to MatrixRTC, we check the following: + and$( + // The client is connected to the sync loop + ( + fromEvent(this.matrixRoom.client, ClientEvent.Sync) as Observable< + [SyncState] + > + ).pipe( + startWith([this.matrixRoom.client.getSyncState()]), + map(([state]) => state === SyncState.Syncing), + ), + // We can see our own call membership + this.memberships$.pipe( + map((ms) => + ms.some( + (m) => m.sender === this.userId && m.deviceId === this.deviceId, + ), ), ), ), ); + // TODO: Account for LiveKit connection state too + private readonly connected$ = this.matrixConnected$; + + /** + * Whether we should tell the user that we're reconnecting to the call. + */ public readonly reconnecting$ = this.scope.behavior( - this.matrixRTCConnected$.pipe( + this.connected$.pipe( // We are reconnecting if we previously had some successful initial // connection but are now disconnected scan( @@ -1533,7 +1554,7 @@ export class CallViewModel extends ViewModel { // Pause all media tracks when we're disconnected from MatrixRTC, because it // can be an unpleasant surprise for the app to say 'reconnecting' and yet // still be transmitting your media to others. - this.matrixRTCConnected$.pipe(this.scope.bind()).subscribe((connected) => { + this.matrixConnected$.pipe(this.scope.bind()).subscribe((connected) => { const publications = this.livekitRoom.localParticipant.trackPublications.values(); if (connected) { diff --git a/src/utils/observable.ts b/src/utils/observable.ts index 22f7c455..1c3a3be7 100644 --- a/src/utils/observable.ts +++ b/src/utils/observable.ts @@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details. import { type Observable, + combineLatest, concat, defer, finalize, @@ -86,3 +87,11 @@ export function getValue(state$: Observable): T { if (value === nothing) throw new Error("Not a state Observable"); return value; } + +/** + * Creates an Observable that has a value of true whenever all its inputs are + * true. + */ +export function and$(...inputs: Observable[]): Observable { + return combineLatest(inputs, (...flags) => flags.every((flag) => flag)); +}