/* Copyright 2021-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 { type FC, useEffect, useState, type ReactNode, useRef, type JSX, } from "react"; import { type MatrixError } from "matrix-js-sdk"; import { logger } from "matrix-js-sdk/lib/logger"; import { Trans, useTranslation } from "react-i18next"; import { CheckIcon, UnknownSolidIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { useClientLegacy } from "../ClientContext"; import { ErrorPage, FullScreenView, LoadingPage } from "../FullScreenView"; import { RoomAuthView } from "./RoomAuthView"; import { GroupCallView } from "./GroupCallView"; import { useRoomIdentifier, useUrlParams } from "../UrlParams"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; import { HomePage } from "../home/HomePage"; import { platform } from "../Platform"; import { AppSelectionModal } from "./AppSelectionModal"; import { widget } from "../widget"; 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"; import { ErrorView } from "../ErrorView"; import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState"; export const RoomPage: FC = () => { const { confineToRoom, appPrompt, preload, header, displayName, skipLobby } = useUrlParams(); const { t } = useTranslation(); const { roomAlias, roomId, viaServers } = useRoomIdentifier(); const roomIdOrAlias = roomId ?? roomAlias; if (!roomIdOrAlias) { logger.error("No room specified"); } const { registerPasswordlessUser } = useRegisterPasswordlessUser(); const [isRegistering, setIsRegistering] = useState(false); const { loading, authenticated, client, error, passwordlessUser } = useClientLegacy(); const { avatarUrl, displayName: userDisplayName } = useProfile(client); const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers); const isJoined = useMatrixRTCSessionJoinState( groupCallState.kind === "loaded" ? groupCallState.rtcSession : undefined, ); const muteStates = useMuteStates(isJoined); useEffect(() => { // If we've finished loading, are not already authed and we've been given a display name as // a URL param, automatically register a passwordless user if (!loading && !authenticated && displayName && !widget) { setIsRegistering(true); registerPasswordlessUser(displayName) .catch((e) => { logger.error("Failed to register passwordless user", e); }) .finally(() => { setIsRegistering(false); }); } }, [ loading, authenticated, displayName, setIsRegistering, registerPasswordlessUser, ]); const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); useEffect(() => { // During the beta, opt into analytics by default if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true); }, [optInAnalytics, setOptInAnalytics]); 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={header !== "standard"} 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 (

That link doesn't appear to belong to any existing call. Check that you have the right link, or{" "} create a new one.

); } else if (groupCallState.error instanceof CallTerminatedMessage) { return (

{groupCallState.error.messageBody}

{groupCallState.error.reason && (

{t("group_call_loader.reason", { reason: groupCallState.error.reason, })}

)}
); } else { return ; } default: return <> ; } }; let content: ReactNode; if (loading || isRegistering) { content = ; } else if (error) { content = ; } else if (!client) { content = ; } else if (!roomIdOrAlias) { // TODO: This doesn't belong here, the app routes need to be reworked content = ; } else { content = groupCallView(); } return ( <> {content} {/* On Android and iOS, show a prompt to launch the mobile app. */} {appPrompt && Config.get().app_prompt && (platform === "android" || platform === "ios") && roomId && } ); };