diff --git a/README.md b/README.md
index f05c2d2d0..43a2dce01 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ rc_message:
MSC3266 allows to request a room summary of rooms you are not joined. The
summary contains the room join rules. We need that to decide if the user gets
-prompted with the option to knock ("ask to join"), a cannot join error or the
+prompted with the option to knock ("Request to join call"), a cannot join error or the
join view.
Element Call requires a Livekit SFU alongside a [Livekit JWT
diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json
index 3c09aa820..dc1b6010f 100644
--- a/public/locales/en-GB/app.json
+++ b/public/locales/en-GB/app.json
@@ -81,8 +81,8 @@
"call_ended_heading": "Call ended",
"failed_heading": "Failed to join",
"failed_text": "Call not found or is not accessible.",
- "knock_reject_body": "The room members declined your request to join.",
- "knock_reject_heading": "Not allowed to join",
+ "knock_reject_body": "Your request to join was declined.",
+ "knock_reject_heading": "Access denied",
"reason": "Reason"
},
"hangup_button_label": "End call",
@@ -100,11 +100,11 @@
"layout_grid_label": "Grid",
"layout_spotlight_label": "Spotlight",
"lobby": {
- "ask_to_join": "Ask to join call",
+ "ask_to_join": "Request to join call",
"join_as_guest": "Join as guest",
"join_button": "Join call",
"leave_button": "Back to recents",
- "waiting_for_invite": "Request sent"
+ "waiting_for_invite": "Request sent! Waiting for permission to join…"
},
"log_in": "Log In",
"logging_in": "Logging in…",
diff --git a/src/room/GroupCallLoader.tsx b/src/room/GroupCallLoader.tsx
deleted file mode 100644
index f843f3f40..000000000
--- a/src/room/GroupCallLoader.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-Copyright 2022-2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only
-Please see LICENSE in the repository root for full details.
-*/
-
-import { MatrixClient } from "matrix-js-sdk/src/client";
-import { useTranslation } from "react-i18next";
-import { MatrixError } from "matrix-js-sdk/src/matrix";
-import { Heading, Text } from "@vector-im/compound-web";
-
-import { Link } from "../button/Link";
-import {
- useLoadGroupCall,
- GroupCallStatus,
- CallTerminatedMessage,
-} from "./useLoadGroupCall";
-import { ErrorView, FullScreenView } from "../FullScreenView";
-
-interface Props {
- client: MatrixClient;
- roomIdOrAlias: string;
- viaServers: string[];
- children: (groupCallState: GroupCallStatus) => JSX.Element;
-}
-
-export function GroupCallLoader({
- client,
- roomIdOrAlias,
- viaServers,
- children,
-}: Props): JSX.Element {
- const { t } = useTranslation();
- const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
-
- switch (groupCallState.kind) {
- case "loaded":
- case "waitForInvite":
- case "canKnock":
- return children(groupCallState);
- case "loading":
- return (
-
-
{t("common.loading")}
-
- );
- case "failed":
- if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
- return (
-
- {t("group_call_loader.failed_heading")}
- {t("group_call_loader.failed_text")}
- {/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
- dupes of this flow, let's make a common component and put it here. */}
- {t("common.home")}
-
- );
- } else if (groupCallState.error instanceof CallTerminatedMessage) {
- return (
-
- {groupCallState.error.message}
- {groupCallState.error.messageBody}
- {groupCallState.error.reason && (
- <>
- {t("group_call_loader.reason")}:
- "{groupCallState.error.reason}"
- >
- )}
- {t("common.home")}
-
- );
- } else {
- return ;
- }
- }
-}
diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx
index 88d791409..86feaaaa6 100644
--- a/src/room/GroupCallView.tsx
+++ b/src/room/GroupCallView.tsx
@@ -177,29 +177,33 @@ export const GroupCallView: FC = ({
}
};
- if (widget && preload && skipLobby) {
- // In preload mode without lobby we wait for a join action before entering
- const onJoin = (ev: CustomEvent): void => {
+ if (skipLobby) {
+ if (widget && preload) {
+ // In preload mode without lobby we wait for a join action before entering
+ const onJoin = (ev: CustomEvent): void => {
+ (async (): Promise => {
+ await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
+ await enterRTCSession(rtcSession, perParticipantE2EE);
+ widget!.api.transport.reply(ev.detail, {});
+ })().catch((e) => {
+ logger.error("Error joining RTC session", e);
+ });
+ };
+ widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
+ return (): void => {
+ widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
+ };
+ } else if (widget && !preload) {
+ // No lobby and no preload: we enter the rtc session right away
(async (): Promise => {
- await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
+ await defaultDeviceSetup({ audioInput: null, videoInput: null });
await enterRTCSession(rtcSession, perParticipantE2EE);
- widget!.api.transport.reply(ev.detail, {});
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
- };
- widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
- return (): void => {
- widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
- };
- } else if (widget && !preload && skipLobby) {
- // No lobby and no preload: we enter the rtc session right away
- (async (): Promise => {
- await defaultDeviceSetup({ audioInput: null, videoInput: null });
- await enterRTCSession(rtcSession, perParticipantE2EE);
- })().catch((e) => {
- logger.error("Error joining RTC session", e);
- });
+ } else {
+ void enterRTCSession(rtcSession, perParticipantE2EE);
+ }
}
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx
index ce6c9f704..49d594bbe 100644
--- a/src/room/RoomPage.tsx
+++ b/src/room/RoomPage.tsx
@@ -5,15 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
-import { FC, useEffect, useState, useCallback, ReactNode } from "react";
+import { FC, useEffect, useState, ReactNode, useRef } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+import { MatrixError } from "matrix-js-sdk/src/http-api";
+import { Heading, Text } from "@vector-im/compound-web";
import { useClientLegacy } from "../ClientContext";
-import { ErrorView, LoadingView } from "../FullScreenView";
+import { ErrorView, FullScreenView, LoadingView } from "../FullScreenView";
import { RoomAuthView } from "./RoomAuthView";
-import { GroupCallLoader } from "./GroupCallLoader";
import { GroupCallView } from "./GroupCallView";
import { useRoomIdentifier, useUrlParams } from "../UrlParams";
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
@@ -21,13 +22,14 @@ import { HomePage } from "../home/HomePage";
import { platform } from "../Platform";
import { AppSelectionModal } from "./AppSelectionModal";
import { widget } from "../widget";
-import { GroupCallStatus } from "./useLoadGroupCall";
+import { CallTerminatedMessage, useLoadGroupCall } from "./useLoadGroupCall";
import { LobbyView } from "./LobbyView";
import { E2eeType } from "../e2ee/e2eeType";
import { useProfile } from "../profile/useProfile";
import { useMuteStates } from "./MuteStates";
import { useOptInAnalytics } from "../settings/settings";
import { Config } from "../config/Config";
+import { Link } from "../button/Link";
export const RoomPage: FC = () => {
const {
@@ -53,6 +55,7 @@ export const RoomPage: FC = () => {
useClientLegacy();
const { avatarUrl, displayName: userDisplayName } = useProfile(client);
+ const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
const muteStates = useMuteStates();
useEffect(() => {
@@ -82,82 +85,112 @@ export const RoomPage: FC = () => {
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
}, [optInAnalytics, setOptInAnalytics]);
- const groupCallView = useCallback(
- (groupCallState: GroupCallStatus): JSX.Element => {
- switch (groupCallState.kind) {
- case "loaded":
+ const wasInWaitForInviteState = useRef(false);
+
+ useEffect(() => {
+ if (groupCallState.kind === "loaded" && wasInWaitForInviteState.current) {
+ logger.log("Play join sound 'Not yet implemented'");
+ }
+ }, [groupCallState.kind]);
+
+ const groupCallView = (): JSX.Element => {
+ switch (groupCallState.kind) {
+ case "loaded":
+ return (
+
+ );
+ case "waitForInvite":
+ case "canKnock": {
+ wasInWaitForInviteState.current =
+ wasInWaitForInviteState.current ||
+ groupCallState.kind === "waitForInvite";
+ const knock =
+ groupCallState.kind === "canKnock" ? groupCallState.knock : null;
+ const label: string | JSX.Element =
+ groupCallState.kind === "canKnock" ? (
+ t("lobby.ask_to_join")
+ ) : (
+ <>
+ {t("lobby.waiting_for_invite")}
+
+ >
+ );
+ return (
+ knock?.()}
+ enterLabel={label}
+ waitingForInvite={groupCallState.kind === "waitForInvite"}
+ confineToRoom={confineToRoom}
+ hideHeader={hideHeader}
+ participantCount={null}
+ muteStates={muteStates}
+ onShareClick={null}
+ />
+ );
+ }
+ case "loading":
+ return (
+
+
{t("common.loading")}
+
+ );
+ case "failed":
+ wasInWaitForInviteState.current = false;
+ if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
return (
-
+
+ {t("group_call_loader.failed_heading")}
+ {t("group_call_loader.failed_text")}
+ {/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
+ dupes of this flow, let's make a common component and put it here. */}
+ {t("common.home")}
+
);
- case "waitForInvite":
- case "canKnock": {
- const knock =
- groupCallState.kind === "canKnock" ? groupCallState.knock : null;
- const label: string | JSX.Element =
- groupCallState.kind === "canKnock" ? (
- t("lobby.ask_to_join")
- ) : (
- <>
- {t("lobby.waiting_for_invite")}
-
- >
- );
+ } else if (groupCallState.error instanceof CallTerminatedMessage) {
return (
- knock?.()}
- enterLabel={label}
- waitingForInvite={groupCallState.kind === "waitForInvite"}
- confineToRoom={confineToRoom}
- hideHeader={hideHeader}
- participantCount={null}
- muteStates={muteStates}
- onShareClick={null}
- />
+
+ {groupCallState.error.message}
+ {groupCallState.error.messageBody}
+ {groupCallState.error.reason && (
+ <>
+ {t("group_call_loader.reason")}:
+ "{groupCallState.error.reason}"
+ >
+ )}
+ {t("common.home")}
+
);
+ } else {
+ return ;
}
- default:
- return <> >;
- }
- },
- [
- client,
- passwordlessUser,
- confineToRoom,
- preload,
- skipLobby,
- hideHeader,
- muteStates,
- t,
- userDisplayName,
- avatarUrl,
- ],
- );
+ default:
+ return <> >;
+ }
+ };
let content: ReactNode;
if (loading || isRegistering) {
@@ -170,15 +203,7 @@ export const RoomPage: FC = () => {
// TODO: This doesn't belong here, the app routes need to be reworked
content = ;
} else {
- content = (
-
- {groupCallView}
-
- );
+ content = groupCallView();
}
return (
diff --git a/src/room/useLoadGroupCall.ts b/src/room/useLoadGroupCall.ts
index 6e07aa528..163571c8a 100644
--- a/src/room/useLoadGroupCall.ts
+++ b/src/room/useLoadGroupCall.ts
@@ -117,8 +117,8 @@ export class CallTerminatedMessage extends Error {
}
export const useLoadGroupCall = (
- client: MatrixClient,
- roomIdOrAlias: string,
+ client: MatrixClient | undefined,
+ roomIdOrAlias: string | null,
viaServers: string[],
): GroupCallStatus => {
const [state, setState] = useState({ kind: "loading" });
@@ -159,6 +159,9 @@ export const useLoadGroupCall = (
?.getContent().reason;
useEffect(() => {
+ if (!client || !roomIdOrAlias) {
+ return;
+ }
const getRoomByAlias = async (alias: string): Promise => {
// We lowercase the localpart when we create the room, so we must lowercase
// it here too (we just do the whole alias). We can't do the same to room IDs