mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
remove disconnect banner from header (we have the in call reconnect
view)
This commit is contained in:
@@ -88,7 +88,7 @@ builds the view model with
|
||||
| Analytics | `src/analytics/PosthogEvents.ts`, `PosthogAnalytics.ts` | `rtcSession.statistics`, account data |
|
||||
| Types only | `src/UrlParams.ts`, `src/state/MediaDevices.ts`, `AndroidControlledAudioOutput.ts`, `IOSControlledAudioOutput.ts`, `initialMuteState.ts`, `state/media/RingingMediaViewModel.ts` (`RTCCallIntent`), `src/useEvents.ts` (`TypedEventEmitter` types) | replaced by a local `CallIntent` type / kept as generic emitter typing |
|
||||
| Runtime misc | `src/useLocalStorage.ts` (`TypedEventEmitter`), `src/room/GroupCallErrorBoundary.tsx` (`MatrixError`), `src/room/KnockLobbyView.tsx` (shell) | see S6 |
|
||||
| Context | `src/ClientContext.tsx` | `useClient`/`useClientState` used by `Avatar`, `sharedKeyManagement`, `useReactionsSender`, `submit-rageshake`, `DisconnectedBanner` |
|
||||
| Context | `src/ClientContext.tsx` | `useClient`/`useClientState` used by `Avatar`, `sharedKeyManagement`, `useReactionsSender`, `submit-rageshake` |
|
||||
|
||||
Hosts: `component/index.tsx:291`, `src/room/useLoadGroupCall.ts:335`,
|
||||
`sdk/main.ts:128` (own `MatrixRTCSessionManager`; waits on
|
||||
@@ -433,7 +433,6 @@ MSC4143: sticky member events, slots, the spec key message), in
|
||||
rageshake requests via `subscribeTimeline`.
|
||||
- `DeveloperSettingsTab`: sticky probe → `getCapabilities()`; custom LiveKit
|
||||
URL validation → `driver.getLivekitToken(...)`.
|
||||
- `DisconnectedBanner` → `HomeserverUnreachable` in `status$` (C12).
|
||||
- `window.rtcSession` debug handle → `window.matrixRtc = { participation }`.
|
||||
|
||||
### 4.5 Hosts
|
||||
@@ -737,7 +736,7 @@ participation, clientDriver, …)` sits next to it; both build a
|
||||
`VideoPreview.tsx`, `useRoomInfo()` (replaces `useRoomName/Avatar/JoinRule/State`),
|
||||
`InviteModal.tsx`, `Avatar.tsx`, `useOwnProfile.ts`, `ProfileSettingsTab.tsx`,
|
||||
`SettingsModal.tsx`, `DeveloperSettingsTab.tsx`, `submit-rageshake.ts`,
|
||||
`DisconnectedBanner.tsx`, `analytics/PosthogEvents.ts`, `controls.ts`, and a
|
||||
`analytics/PosthogEvents.ts`, `controls.ts`, and a
|
||||
first `CallView.stories.tsx` (lobby, in call, ended) driven by
|
||||
`MockMatrixDriver`.
|
||||
- **S4b ☑ (2026-09-16):** `ReactionsSenderProvider` takes `ownIdentifier`,
|
||||
@@ -785,13 +784,14 @@ roomInfo.encrypted)` (`useRoomEncryptionSystem` keeps the client for the
|
||||
client driver's public `client`/`room` are gone. **Consequence:** the
|
||||
component and its dev harness (`ElementCallClientBased`) now run every
|
||||
call on the crate.
|
||||
- **Banner on the driver (2026-09-16):** `useHomeserverConnected(drivers,
|
||||
graceMs)` (`src/driver/`) follows `rtcDriver.isHomeserverConnected()` /
|
||||
`subscribeConnectivity` and reports a lapse only after
|
||||
`sync_disconnect_grace_period_ms`, since the driver reports every sync
|
||||
hiccup where the client state waited for a `ConnectionError`;
|
||||
`DisconnectedBanner` uses it whenever drivers are provided and falls back
|
||||
to the client state for the shell. The mock RTC driver keeps several
|
||||
- **Banner dropped (2026-09-17):** the disconnected banner, its
|
||||
`useHomeserverConnected` hook and the client state's `disconnected` flag
|
||||
are gone. The local membership already carries the connection state, so
|
||||
the reconnecting overlay covers the in-call case where it matters; in the
|
||||
lobby the banner added little. `HomeserverConnected` in
|
||||
`state/CallViewModel/localMember/` remains the one consumer of
|
||||
`rtcDriver.isHomeserverConnected()` / `subscribeConnectivity` and of
|
||||
`sync_disconnect_grace_period_ms`. The mock RTC driver keeps several
|
||||
connectivity sinks (the crate's and the UI's).
|
||||
- **S4 closed (2026-09-16):** `CallView.stories.tsx` (Lobby with a peer,
|
||||
NoTransport as the error path, Ended) over the mock drivers, no client;
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
"show_connection_stats": "Show connection statistics",
|
||||
"url_params": "URL parameters"
|
||||
},
|
||||
"disconnected_banner": "Connectivity to the server has been lost.",
|
||||
"error": {
|
||||
"call_is_not_supported": "Call is not supported",
|
||||
"call_not_found": "Call not found",
|
||||
|
||||
@@ -29,7 +29,6 @@ const TestComponent: FC<
|
||||
<ClientContextProvider
|
||||
value={{
|
||||
state: "valid",
|
||||
disconnected: false,
|
||||
supportedFeatures: {
|
||||
reactions: true,
|
||||
},
|
||||
|
||||
+2
-32
@@ -17,8 +17,7 @@ import {
|
||||
type JSX,
|
||||
} from "react";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { type ISyncStateData, type SyncState } from "matrix-js-sdk/lib/sync";
|
||||
import { ClientEvent, type MatrixClient } from "matrix-js-sdk";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
|
||||
import { ErrorPage } from "./FullScreenView";
|
||||
import { useHostBridge } from "./HostBridge";
|
||||
@@ -42,9 +41,6 @@ export type ClientState = ValidClientState | ErrorState;
|
||||
export type ValidClientState = {
|
||||
state: "valid";
|
||||
authenticated?: AuthenticatedClient;
|
||||
// 'Disconnected' rather than 'connected' because it tracks specifically
|
||||
// whether the client is supposed to be connected but is not
|
||||
disconnected: boolean;
|
||||
supportedFeatures: {
|
||||
reactions: boolean;
|
||||
};
|
||||
@@ -276,7 +272,6 @@ export const ClientProvider: FC<Props> = ({ children, client }) => {
|
||||
}, [initClientState?.client, setAlreadyOpenedErr]),
|
||||
);
|
||||
|
||||
const [isDisconnected, setIsDisconnected] = useState(false);
|
||||
const [supportsReactions, setSupportsReactions] = useState(false);
|
||||
|
||||
const state: ClientState | undefined = useMemo(() => {
|
||||
@@ -300,7 +295,6 @@ export const ClientProvider: FC<Props> = ({ children, client }) => {
|
||||
state: "valid",
|
||||
authenticated,
|
||||
setClient,
|
||||
disconnected: isDisconnected,
|
||||
supportedFeatures: {
|
||||
reactions: supportsReactions,
|
||||
},
|
||||
@@ -311,17 +305,9 @@ export const ClientProvider: FC<Props> = ({ children, client }) => {
|
||||
initClientState,
|
||||
logout,
|
||||
setClient,
|
||||
isDisconnected,
|
||||
supportsReactions,
|
||||
]);
|
||||
|
||||
const onSync = useCallback(
|
||||
(state: SyncState, _old: SyncState | null, data?: ISyncStateData) => {
|
||||
setIsDisconnected(clientIsDisconnected(state, data));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initClientState) {
|
||||
return;
|
||||
@@ -333,20 +319,10 @@ export const ClientProvider: FC<Props> = ({ children, client }) => {
|
||||
if (PosthogAnalytics.hasInstance())
|
||||
PosthogAnalytics.instance.onLoginStatusChanged();
|
||||
|
||||
if (initClientState.client) {
|
||||
initClientState.client.on(ClientEvent.Sync, onSync);
|
||||
}
|
||||
|
||||
if (!hostBridge.supportsReactions)
|
||||
logger.warn("The host does not permit reactions");
|
||||
setSupportsReactions(hostBridge.supportsReactions);
|
||||
|
||||
return (): void => {
|
||||
if (initClientState.client) {
|
||||
initClientState.client.removeListener(ClientEvent.Sync, onSync);
|
||||
}
|
||||
};
|
||||
}, [initClientState, onSync, hostBridge]);
|
||||
}, [initClientState, hostBridge]);
|
||||
|
||||
if (alreadyOpenedErr) {
|
||||
return <ErrorPage error={alreadyOpenedErr} />;
|
||||
@@ -388,9 +364,3 @@ const loadSession = (): Session | undefined => {
|
||||
|
||||
return JSON.parse(data);
|
||||
};
|
||||
|
||||
const clientIsDisconnected = (
|
||||
syncState: SyncState,
|
||||
syncData?: ISyncStateData,
|
||||
): boolean =>
|
||||
syncState === "ERROR" && syncData?.error?.name === "ConnectionError";
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
Copyright 2023, 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
.banner {
|
||||
position: absolute;
|
||||
padding: 29px;
|
||||
background-color: var(--cpd-color-bg-subtle-primary);
|
||||
vertical-align: middle;
|
||||
font-size: var(--font-size-body);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
top: 76px;
|
||||
width: calc(100% - 58px);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright 2023, 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import { type FC, type HTMLAttributes, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./DisconnectedBanner.module.css";
|
||||
import { type ValidClientState, useClientState } from "./ClientContext";
|
||||
import { useOptionalMatrixDrivers } from "./driver/MatrixDriverContext";
|
||||
import { useHomeserverConnected } from "./driver/useHomeserverConnected";
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLElement> {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DisconnectedBanner: FC<Props> = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
// Under a call the host's RTC driver says whether the homeserver is
|
||||
// reachable; the shell outside a call only has the client's sync state.
|
||||
const drivers = useOptionalMatrixDrivers();
|
||||
const homeserverConnected = useHomeserverConnected(drivers);
|
||||
const clientState = useClientState();
|
||||
let shouldShowBanner = false;
|
||||
|
||||
if (drivers !== null) {
|
||||
shouldShowBanner = !homeserverConnected;
|
||||
} else if (clientState?.state === "valid") {
|
||||
const validClientState = clientState as ValidClientState;
|
||||
shouldShowBanner = validClientState.disconnected;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldShowBanner && (
|
||||
<div className={classNames(styles.banner, className)} {...rest}>
|
||||
{children}
|
||||
{t("disconnected_banner")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-18
@@ -23,38 +23,23 @@ import { Avatar, Size } from "./Avatar";
|
||||
import { EncryptionLock } from "./room/EncryptionLock";
|
||||
import { useRootSizeMatches } from "./useRootSize";
|
||||
import { useLeaveToHome } from "./LeaveToHomeContext";
|
||||
import { DisconnectedBanner } from "./DisconnectedBanner";
|
||||
|
||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||
ref?: Ref<HTMLElement>;
|
||||
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<HeaderProps> = ({
|
||||
ref,
|
||||
children,
|
||||
className,
|
||||
disconnectedBanner = true,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
ref={ref}
|
||||
className={classNames(styles.header, className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</header>
|
||||
{disconnectedBanner && <DisconnectedBanner />}
|
||||
</>
|
||||
<header ref={ref} className={classNames(styles.header, className)} {...rest}>
|
||||
{children}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { type MatrixDrivers } from "./MatrixDriverContext";
|
||||
import { MockElementCallMatrixClientDriver } from "./MockElementCallMatrixClientDriver";
|
||||
import { MockRtcMatrixDriver } from "./MockRtcMatrixDriver";
|
||||
import { useHomeserverConnected } from "./useHomeserverConnected";
|
||||
|
||||
describe("useHomeserverConnected", () => {
|
||||
beforeEach(() => vi.useFakeTimers());
|
||||
afterEach(() => vi.useRealTimers());
|
||||
|
||||
it("reports a lapse only after the grace period, and a return at once", () => {
|
||||
const rtcDriver = new MockRtcMatrixDriver();
|
||||
const drivers: MatrixDrivers = {
|
||||
rtcDriver,
|
||||
clientDriver: new MockElementCallMatrixClientDriver(),
|
||||
};
|
||||
const { result } = renderHook(() => useHomeserverConnected(drivers, 1000));
|
||||
expect(result.current).toBe(true);
|
||||
|
||||
act(() => rtcDriver.setHomeserverConnected(false));
|
||||
expect(result.current).toBe(true);
|
||||
act(() => vi.advanceTimersByTime(999));
|
||||
expect(result.current).toBe(true);
|
||||
act(() => vi.advanceTimersByTime(1));
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
act(() => rtcDriver.setHomeserverConnected(true));
|
||||
expect(result.current).toBe(true);
|
||||
|
||||
// A blip shorter than the grace period is never shown.
|
||||
act(() => rtcDriver.setHomeserverConnected(false));
|
||||
act(() => vi.advanceTimersByTime(500));
|
||||
act(() => rtcDriver.setHomeserverConnected(true));
|
||||
act(() => vi.advanceTimersByTime(1000));
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it("starts from the driver's current answer", () => {
|
||||
const rtcDriver = new MockRtcMatrixDriver();
|
||||
rtcDriver.setHomeserverConnected(false);
|
||||
const drivers: MatrixDrivers = {
|
||||
rtcDriver,
|
||||
clientDriver: new MockElementCallMatrixClientDriver(),
|
||||
};
|
||||
const { result } = renderHook(() => useHomeserverConnected(drivers, 1000));
|
||||
act(() => vi.advanceTimersByTime(1000));
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it("is connected without drivers", () => {
|
||||
const { result } = renderHook(() => useHomeserverConnected(null, 1000));
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
import { type MatrixDrivers } from "./MatrixDriverContext";
|
||||
|
||||
/**
|
||||
* Whether the homeserver is reachable, from the RTC driver's connectivity
|
||||
* (the same signal the crate turns into `HomeserverUnreachable`). The driver
|
||||
* reports every lapse of the sync loop; the UI should not, so a lapse counts
|
||||
* only once it has lasted `graceMs` (the deployment's
|
||||
* `sync_disconnect_grace_period_ms` by default). Coming back counts at once.
|
||||
*
|
||||
* `null` drivers (a host that provided none) read as connected: there is
|
||||
* nothing to report on.
|
||||
*/
|
||||
export function useHomeserverConnected(
|
||||
drivers: MatrixDrivers | null,
|
||||
graceMs: number = Config.get().sync_disconnect_grace_period_ms,
|
||||
): boolean {
|
||||
const [connected, setConnected] = useState(true);
|
||||
useEffect(() => {
|
||||
if (drivers === null) {
|
||||
setConnected(true);
|
||||
return;
|
||||
}
|
||||
const { rtcDriver } = drivers;
|
||||
let live = true;
|
||||
let lapse: ReturnType<typeof setTimeout> | null = null;
|
||||
const report = (isConnected: boolean): void => {
|
||||
if (lapse !== null) {
|
||||
clearTimeout(lapse);
|
||||
lapse = null;
|
||||
}
|
||||
if (isConnected) setConnected(true);
|
||||
else lapse = setTimeout(() => setConnected(false), graceMs);
|
||||
};
|
||||
report(rtcDriver.isHomeserverConnected());
|
||||
rtcDriver.subscribeConnectivity({
|
||||
emit: (isConnected) => {
|
||||
if (!live) return false;
|
||||
report(isConnected);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
return (): void => {
|
||||
live = false;
|
||||
if (lapse !== null) clearTimeout(lapse);
|
||||
};
|
||||
}, [drivers, graceMs]);
|
||||
return connected;
|
||||
}
|
||||
@@ -505,7 +505,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
[styles.hidden]: !showHeader,
|
||||
})}
|
||||
ref={headerRef}
|
||||
disconnectedBanner={false} // This screen has its own 'reconnecting' toast
|
||||
>
|
||||
<LeftNav>
|
||||
<RoomHeaderInfo
|
||||
|
||||
@@ -77,7 +77,6 @@ function renderWithMockClient(
|
||||
<ClientContextProvider
|
||||
value={{
|
||||
state: "valid",
|
||||
disconnected: false,
|
||||
supportedFeatures: {
|
||||
reactions: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user