mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-08 20:09:19 +00:00
Add error screens for connecting to the JWT service
This commit is contained in:
@@ -72,16 +72,22 @@
|
|||||||
},
|
},
|
||||||
"disconnected_banner": "Connectivity to the server has been lost.",
|
"disconnected_banner": "Connectivity to the server has been lost.",
|
||||||
"error": {
|
"error": {
|
||||||
|
"auth_connection_failed_details": "<0>The application could not reach the call authentication service at <2>{{url}}</2>. If you are the server admin, check the network logs and make sure <5>lk-jwt-service</5> is listening at that address.</0>",
|
||||||
|
"auth_connection_rejected_details": "<0>The application connected to the call authentication service at <2>{{url}}</2>, but it responded with status code {{status}}. If you are the server admin, make sure <8>lk-jwt-service</8> is listening at that address and check the logs for more information.</0>",
|
||||||
"call_not_found": "Call not found",
|
"call_not_found": "Call not found",
|
||||||
"call_not_found_description": "<0>That link doesn't appear to belong to any existing call. Check that you have the right link, or <1>create a new one</1>.</0>",
|
"call_not_found_description": "<0>That link doesn't appear to belong to any existing call. Check that you have the right link, or <1>create a new one</1>.</0>",
|
||||||
|
"connection_failed": "Connection failed",
|
||||||
|
"connection_failed_description": "Could not connect to the call server. Check your network connection or try again later.",
|
||||||
"connection_lost": "Connection lost",
|
"connection_lost": "Connection lost",
|
||||||
"connection_lost_description": "You were disconnected from the call.",
|
"connection_lost_description": "You were disconnected from the call.",
|
||||||
|
"connection_rejected_description": "Could not connect to the call server. Try again later or contact your server admin if this persists.",
|
||||||
"e2ee_unsupported": "Incompatible browser",
|
"e2ee_unsupported": "Incompatible browser",
|
||||||
"e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
|
"e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
|
||||||
"generic": "Something went wrong",
|
"generic": "Something went wrong",
|
||||||
"generic_description": "Submitting debug logs will help us track down the problem.",
|
"generic_description": "Submitting debug logs will help us track down the problem.",
|
||||||
"open_elsewhere": "Opened in another tab",
|
"open_elsewhere": "Opened in another tab",
|
||||||
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page."
|
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.",
|
||||||
|
"show_details": "Show details"
|
||||||
},
|
},
|
||||||
"group_call_loader": {
|
"group_call_loader": {
|
||||||
"banned_body": "You have been banned from the room.",
|
"banned_body": "You have been banned from the room.",
|
||||||
|
|||||||
@@ -5,9 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type FC, type ReactNode } from "react";
|
import {
|
||||||
import { useTranslation } from "react-i18next";
|
type ReactElement,
|
||||||
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
useCallback,
|
||||||
|
useState,
|
||||||
|
type FC,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
OfflineIcon,
|
||||||
|
PopOutIcon,
|
||||||
|
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
import { Button, Link } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { ErrorView } from "./ErrorView";
|
import { ErrorView } from "./ErrorView";
|
||||||
|
|
||||||
@@ -22,8 +32,9 @@ export class RichError extends Error {
|
|||||||
* The pretty, more helpful message to be shown on the error screen.
|
* The pretty, more helpful message to be shown on the error screen.
|
||||||
*/
|
*/
|
||||||
public readonly richMessage: ReactNode,
|
public readonly richMessage: ReactNode,
|
||||||
|
cause?: unknown,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message, { cause });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,3 +57,117 @@ export class OpenElsewhereError extends RichError {
|
|||||||
super("App opened in another tab", <OpenElsewhere />);
|
super("App opened in another tab", <OpenElsewhere />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuthConnectionFailedProps {
|
||||||
|
livekitServiceUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthConnectionFailed: FC<AuthConnectionFailedProps> = ({
|
||||||
|
livekitServiceUrl,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
|
const onShowDetailsClick = useCallback(() => setShowDetails(true), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorView Icon={OfflineIcon} title={t("error.connection_failed")}>
|
||||||
|
<p>{t("error.connection_failed_description")}</p>
|
||||||
|
{showDetails ? (
|
||||||
|
<Trans
|
||||||
|
i18nKey="error.auth_connection_failed_details"
|
||||||
|
url={livekitServiceUrl}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
The application could not reach the call authentication service at{" "}
|
||||||
|
<Link href={livekitServiceUrl} target="_blank">
|
||||||
|
{{ url: livekitServiceUrl } as unknown as ReactElement}
|
||||||
|
</Link>
|
||||||
|
. If you are the server admin, check the network logs and make sure{" "}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/element-hq/lk-jwt-service/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
lk-jwt-service
|
||||||
|
</Link>{" "}
|
||||||
|
is listening at that address.
|
||||||
|
</p>
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Button kind="tertiary" onClick={onShowDetailsClick}>
|
||||||
|
{t("error.show_details")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ErrorView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AuthConnectionFailedError extends RichError {
|
||||||
|
public constructor(livekitServiceUrl: string, cause: unknown) {
|
||||||
|
super(
|
||||||
|
`Failed to connect to ${livekitServiceUrl}`,
|
||||||
|
<AuthConnectionFailed livekitServiceUrl={livekitServiceUrl} />,
|
||||||
|
cause,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthConnectionRejectedProps {
|
||||||
|
livekitServiceUrl: string;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthConnectionRejected: FC<AuthConnectionRejectedProps> = ({
|
||||||
|
livekitServiceUrl,
|
||||||
|
status,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
|
const onShowDetailsClick = useCallback(() => setShowDetails(true), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorView Icon={OfflineIcon} title={t("error.connection_failed")}>
|
||||||
|
<p>{t("error.connection_rejected_description")}</p>
|
||||||
|
{showDetails ? (
|
||||||
|
<Trans
|
||||||
|
i18nKey="error.auth_connection_rejected_details"
|
||||||
|
url={livekitServiceUrl}
|
||||||
|
status={status}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
The application connected to the call authentication service at{" "}
|
||||||
|
<Link href={livekitServiceUrl} target="_blank">
|
||||||
|
{{ url: livekitServiceUrl } as unknown as ReactElement}
|
||||||
|
</Link>
|
||||||
|
, but it responded with status code{" "}
|
||||||
|
{{ status } as unknown as ReactElement}. If you are the server
|
||||||
|
admin, make sure{" "}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/element-hq/lk-jwt-service/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
lk-jwt-service
|
||||||
|
</Link>{" "}
|
||||||
|
is listening at that address and check the logs for more
|
||||||
|
information.
|
||||||
|
</p>
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Button kind="tertiary" onClick={onShowDetailsClick}>
|
||||||
|
{t("error.show_details")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ErrorView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AuthConnectionRejectedError extends RichError {
|
||||||
|
public constructor(livekitServiceUrl: string, status: number) {
|
||||||
|
super(
|
||||||
|
`Failed to connect to ${livekitServiceUrl} (status ${status})`,
|
||||||
|
<AuthConnectionRejected
|
||||||
|
livekitServiceUrl={livekitServiceUrl}
|
||||||
|
status={status}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import { useEffect, useState } from "react";
|
|||||||
import { type LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
import { type LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
||||||
|
|
||||||
import { useActiveLivekitFocus } from "../room/useActiveFocus";
|
import { useActiveLivekitFocus } from "../room/useActiveFocus";
|
||||||
|
import {
|
||||||
|
AuthConnectionFailedError,
|
||||||
|
AuthConnectionRejectedError,
|
||||||
|
} from "../RichError";
|
||||||
|
|
||||||
export interface SFUConfig {
|
export interface SFUConfig {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -35,25 +39,24 @@ export function useOpenIDSFU(
|
|||||||
client: OpenIDClientParts,
|
client: OpenIDClientParts,
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
): SFUConfig | undefined {
|
): SFUConfig | undefined {
|
||||||
const [sfuConfig, setSFUConfig] = useState<SFUConfig | undefined>(undefined);
|
const [sfuConfig, setSFUConfig] = useState<SFUConfig | Error | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const activeFocus = useActiveLivekitFocus(rtcSession);
|
const activeFocus = useActiveLivekitFocus(rtcSession);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeFocus) {
|
if (activeFocus) {
|
||||||
getSFUConfigWithOpenID(client, activeFocus).then(
|
getSFUConfigWithOpenID(client, activeFocus).then(
|
||||||
(sfuConfig) => {
|
(sfuConfig) => setSFUConfig(sfuConfig),
|
||||||
setSFUConfig(sfuConfig);
|
(e) => setSFUConfig(e),
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
logger.error("Failed to get SFU config", e);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setSFUConfig(undefined);
|
setSFUConfig(undefined);
|
||||||
}
|
}
|
||||||
}, [client, activeFocus]);
|
}, [client, activeFocus]);
|
||||||
|
|
||||||
|
if (sfuConfig instanceof Error) throw sfuConfig;
|
||||||
return sfuConfig;
|
return sfuConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,26 +67,18 @@ export async function getSFUConfigWithOpenID(
|
|||||||
const openIdToken = await client.getOpenIdToken();
|
const openIdToken = await client.getOpenIdToken();
|
||||||
logger.debug("Got openID token", openIdToken);
|
logger.debug("Got openID token", openIdToken);
|
||||||
|
|
||||||
try {
|
logger.info(
|
||||||
logger.info(
|
`Trying to get JWT from call's active focus URL of ${activeFocus.livekit_service_url}...`,
|
||||||
`Trying to get JWT from call's active focus URL of ${activeFocus.livekit_service_url}...`,
|
);
|
||||||
);
|
const sfuConfig = await getLiveKitJWT(
|
||||||
const sfuConfig = await getLiveKitJWT(
|
client,
|
||||||
client,
|
activeFocus.livekit_service_url,
|
||||||
activeFocus.livekit_service_url,
|
activeFocus.livekit_alias,
|
||||||
activeFocus.livekit_alias,
|
openIdToken,
|
||||||
openIdToken,
|
);
|
||||||
);
|
logger.info(`Got JWT from call's active focus URL.`);
|
||||||
logger.info(`Got JWT from call's active focus URL.`);
|
|
||||||
|
|
||||||
return sfuConfig;
|
return sfuConfig;
|
||||||
} catch (e) {
|
|
||||||
logger.warn(
|
|
||||||
`Failed to get JWT from RTC session's active focus URL of ${activeFocus.livekit_service_url}.`,
|
|
||||||
e,
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLiveKitJWT(
|
async function getLiveKitJWT(
|
||||||
@@ -92,8 +87,9 @@ async function getLiveKitJWT(
|
|||||||
roomName: string,
|
roomName: string,
|
||||||
openIDToken: IOpenIDToken,
|
openIDToken: IOpenIDToken,
|
||||||
): Promise<SFUConfig> {
|
): Promise<SFUConfig> {
|
||||||
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(livekitServiceURL + "/sfu/get", {
|
res = await fetch(livekitServiceURL + "/sfu/get", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -104,11 +100,11 @@ async function getLiveKitJWT(
|
|||||||
device_id: client.getDeviceId(),
|
device_id: client.getDeviceId(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("SFU Config fetch failed with status code " + res.status);
|
|
||||||
}
|
|
||||||
return await res.json();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("SFU Config fetch failed with exception " + e);
|
throw new AuthConnectionFailedError(livekitServiceURL, e);
|
||||||
}
|
}
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new AuthConnectionRejectedError(livekitServiceURL, res.status);
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user