Merge branch 'livekit' into test-call-vm
This commit is contained in:
@@ -9,12 +9,11 @@ import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { Button, Heading, Text } from "@vector-im/compound-web";
|
||||
|
||||
import styles from "./CallEndedView.module.css";
|
||||
import feedbackStyle from "../input/FeedbackInput.module.css";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { Body, Headline } from "../typography/Typography";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
@@ -139,11 +138,11 @@ export const CallEndedView: FC<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
<Heading size="xl" weight="semibold" className={styles.headline}>
|
||||
<Trans i18nKey="call_ended_view.body">
|
||||
You were disconnected from the call
|
||||
</Trans>
|
||||
</Headline>
|
||||
</Heading>
|
||||
<div className={styles.disconnectedButtons}>
|
||||
<Button onClick={reconnect}>
|
||||
{t("call_ended_view.reconnect_button")}
|
||||
@@ -154,9 +153,9 @@ export const CallEndedView: FC<Props> = ({
|
||||
</div>
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Body className={styles.footer}>
|
||||
<Text className={styles.footer}>
|
||||
<Link to="/"> {t("return_home_button")} </Link>
|
||||
</Body>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -164,7 +163,7 @@ export const CallEndedView: FC<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
<Heading size="xl" weight="semibold" className={styles.headline}>
|
||||
{surveySubmitted
|
||||
? t("call_ended_view.headline", {
|
||||
displayName,
|
||||
@@ -174,16 +173,16 @@ export const CallEndedView: FC<Props> = ({
|
||||
}) +
|
||||
"\n" +
|
||||
t("call_ended_view.survey_prompt")}
|
||||
</Headline>
|
||||
</Heading>
|
||||
{(!surveySubmitted || confineToRoom) &&
|
||||
PosthogAnalytics.instance.isEnabled()
|
||||
? qualitySurveyDialog
|
||||
: createAccountDialog}
|
||||
</main>
|
||||
{!confineToRoom && (
|
||||
<Body className={styles.footer}>
|
||||
<Text className={styles.footer}>
|
||||
<Link to="/"> {t("call_ended_view.not_now_button")} </Link>
|
||||
</Body>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,13 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Heading, Link, Text } from "@vector-im/compound-web";
|
||||
import { Heading, Text } from "@vector-im/compound-web";
|
||||
|
||||
import { Link } from "../button/Link";
|
||||
import {
|
||||
useLoadGroupCall,
|
||||
GroupCallStatus,
|
||||
@@ -35,15 +34,6 @@ export function GroupCallLoader({
|
||||
const { t } = useTranslation();
|
||||
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
|
||||
|
||||
const history = useHistory();
|
||||
const onHomeClick = useCallback(
|
||||
(ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
history.push("/");
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
switch (groupCallState.kind) {
|
||||
case "loaded":
|
||||
case "waitForInvite":
|
||||
@@ -63,9 +53,7 @@ export function GroupCallLoader({
|
||||
<Text>{t("group_call_loader.failed_text")}</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. */}
|
||||
<Link href="/" onClick={onHomeClick}>
|
||||
{t("common.home")}
|
||||
</Link>
|
||||
<Link to="/">{t("common.home")}</Link>
|
||||
</FullScreenView>
|
||||
);
|
||||
} else if (groupCallState.error instanceof CallTerminatedMessage) {
|
||||
@@ -79,9 +67,7 @@ export function GroupCallLoader({
|
||||
<Text size="sm">"{groupCallState.error.reason}"</Text>
|
||||
</>
|
||||
)}
|
||||
<Link href="/" onClick={onHomeClick}>
|
||||
{t("common.home")}
|
||||
</Link>
|
||||
<Link to="/">{t("common.home")}</Link>
|
||||
</FullScreenView>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||
import { Heading, Link, Text } from "@vector-im/compound-web";
|
||||
import { Heading, Text } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
@@ -40,6 +40,7 @@ import { useJoinRule } from "./useJoinRule";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { Link } from "../button/Link";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -85,6 +86,14 @@ export const GroupCallView: FC<Props> = ({
|
||||
};
|
||||
}, [rtcSession]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sanity check the room object
|
||||
if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room)
|
||||
logger.warn(
|
||||
`We've ended up with multiple rooms for the same ID (${rtcSession.room.roomId}). This indicates a bug in the group call loading code, and may lead to incomplete room state.`,
|
||||
);
|
||||
}, [client, rtcSession.room]);
|
||||
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const roomName = useRoomName(rtcSession.room);
|
||||
const roomAvatar = useRoomAvatar(rtcSession.room);
|
||||
@@ -273,14 +282,6 @@ export const GroupCallView: FC<Props> = ({
|
||||
);
|
||||
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
|
||||
|
||||
const onHomeClick = useCallback(
|
||||
(ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
history.push("/");
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) {
|
||||
@@ -289,9 +290,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
<FullScreenView>
|
||||
<Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading>
|
||||
<Text>{t("browser_media_e2ee_unsupported")}</Text>
|
||||
<Link href="/" onClick={onHomeClick}>
|
||||
{t("common.home")}
|
||||
</Link>
|
||||
<Link to="/">{t("common.home")}</Link>
|
||||
</FullScreenView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { FC, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
|
||||
import { Modal, Props as ModalProps } from "../Modal";
|
||||
import { FieldRow, ErrorMessage } from "../input/Input";
|
||||
import { useSubmitRageshake } from "../settings/submit-rageshake";
|
||||
import { Body } from "../typography/Typography";
|
||||
|
||||
interface Props extends Omit<ModalProps, "title" | "children"> {
|
||||
rageshakeRequestId: string;
|
||||
@@ -40,7 +39,7 @@ export const RageshakeRequestModal: FC<Props> = ({
|
||||
open={open}
|
||||
onDismiss={onDismiss}
|
||||
>
|
||||
<Body>{t("rageshake_request_modal.body")}</Body>
|
||||
<Text>{t("rageshake_request_modal.body")}</Text>
|
||||
<FieldRow>
|
||||
<Button
|
||||
onClick={(): void =>
|
||||
|
||||
@@ -9,16 +9,16 @@ import { FC, useCallback, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { Button, Heading, Text } from "@vector-im/compound-web";
|
||||
|
||||
import styles from "./RoomAuthView.module.css";
|
||||
import { Body, Caption, Link, Headline } from "../typography/Typography";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { Form } from "../form/Form";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
||||
import { Config } from "../config/Config";
|
||||
import { ExternalLink, Link } from "../button/Link";
|
||||
|
||||
export const RoomAuthView: FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -63,9 +63,9 @@ export const RoomAuthView: FC = () => {
|
||||
</Header>
|
||||
<div className={styles.container}>
|
||||
<main className={styles.main}>
|
||||
<Headline className={styles.headline}>
|
||||
<Heading size="xl" weight="semibold" className={styles.headline}>
|
||||
{t("lobby.join_button")}
|
||||
</Headline>
|
||||
</Heading>
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
@@ -79,14 +79,14 @@ export const RoomAuthView: FC = () => {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FieldRow>
|
||||
<Caption>
|
||||
<Text size="sm">
|
||||
<Trans i18nKey="room_auth_view_eula_caption">
|
||||
By clicking "Join call now", you agree to our{" "}
|
||||
<Link href={Config.get().eula}>
|
||||
<ExternalLink href={Config.get().eula}>
|
||||
End User Licensing Agreement (EULA)
|
||||
</Link>
|
||||
</ExternalLink>
|
||||
</Trans>
|
||||
</Caption>
|
||||
</Text>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage error={error} />
|
||||
@@ -103,17 +103,14 @@ export const RoomAuthView: FC = () => {
|
||||
<div id={recaptchaId} />
|
||||
</Form>
|
||||
</main>
|
||||
<Body className={styles.footer}>
|
||||
<Text className={styles.footer}>
|
||||
<Trans i18nKey="unauthenticated_view_body">
|
||||
Not registered yet?{" "}
|
||||
<Link
|
||||
color="primary"
|
||||
to={{ pathname: "/register", state: { from: location } }}
|
||||
>
|
||||
<Link to="/register" state={{ from: location }}>
|
||||
Create an account
|
||||
</Link>
|
||||
</Trans>
|
||||
</Body>
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { RoomEvent, Room } from "matrix-js-sdk/src/models/room";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||
import { JoinRule, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { widget } from "../widget";
|
||||
@@ -54,6 +54,42 @@ export type GroupCallStatus =
|
||||
| GroupCallWaitForInvite
|
||||
| GroupCallCanKnock;
|
||||
|
||||
const MAX_ATTEMPTS_FOR_INVITE_JOIN_FAILURE = 3;
|
||||
const DELAY_MS_FOR_INVITE_JOIN_FAILURE = 3000;
|
||||
|
||||
/**
|
||||
* Join a room, and retry on M_FORBIDDEN error in order to work
|
||||
* around a potential race when joining rooms over federation.
|
||||
*
|
||||
* Will wait up to to `DELAY_MS_FOR_INVITE_JOIN_FAILURE` per attempt.
|
||||
* Will try up to `MAX_ATTEMPTS_FOR_INVITE_JOIN_FAILURE` times.
|
||||
*
|
||||
* @see https://github.com/element-hq/element-call/issues/2634
|
||||
* @param client The matrix client
|
||||
* @param attempt Number of attempts made.
|
||||
* @param params Parameters to pass to client.joinRoom
|
||||
*/
|
||||
async function joinRoomAfterInvite(
|
||||
client: MatrixClient,
|
||||
attempt = 0,
|
||||
...params: Parameters<MatrixClient["joinRoom"]>
|
||||
): ReturnType<MatrixClient["joinRoom"]> {
|
||||
try {
|
||||
return await client.joinRoom(...params);
|
||||
} catch (ex) {
|
||||
if (
|
||||
ex instanceof MatrixError &&
|
||||
ex.errcode === "M_FORBIDDEN" &&
|
||||
attempt < MAX_ATTEMPTS_FOR_INVITE_JOIN_FAILURE
|
||||
) {
|
||||
// If we were invited and got a M_FORBIDDEN, it's highly likely the server hasn't caught up yet.
|
||||
await new Promise((r) => setTimeout(r, DELAY_MS_FOR_INVITE_JOIN_FAILURE));
|
||||
return joinRoomAfterInvite(client, attempt + 1, ...params);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
export class CallTerminatedMessage extends Error {
|
||||
/**
|
||||
* @param messageBody The message explaining the kind of termination (kick, ban, knock reject, etc.) (translated)
|
||||
@@ -162,10 +198,13 @@ export const useLoadGroupCall = (
|
||||
membership === KnownMembership.Invite &&
|
||||
prevMembership === KnownMembership.Knock
|
||||
) {
|
||||
client.joinRoom(room.roomId, { viaServers }).then((room) => {
|
||||
logger.log("Auto-joined %s", room.roomId);
|
||||
resolve(room);
|
||||
}, reject);
|
||||
joinRoomAfterInvite(client, 0, room.roomId, { viaServers }).then(
|
||||
(room) => {
|
||||
logger.log("Auto-joined %s", room.roomId);
|
||||
resolve(room);
|
||||
},
|
||||
reject,
|
||||
);
|
||||
}
|
||||
if (membership === KnownMembership.Ban) reject(bannedError());
|
||||
if (membership === KnownMembership.Leave)
|
||||
|
||||
Reference in New Issue
Block a user