From 4ca99f0e59fa6e10d13ecd36ff11bd7c41dc40cc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 20 Jan 2025 17:25:46 +0000 Subject: [PATCH] For discussion --- locales/en/app.json | 8 +- src/RichError.tsx | 271 ++++++++++++++++++++++++++------------- src/livekit/openIDSFU.ts | 35 +++-- 3 files changed, 209 insertions(+), 105 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 7552e4b9..e90b3559 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -72,8 +72,12 @@ }, "disconnected_banner": "Connectivity to the server has been lost.", "error": { - "auth_connection_failed_details": "<0>The application could not reach the call authentication service at <2>{{url}}. If you are the server admin, check the network logs and make sure <5>lk-jwt-service is listening at that address.", - "auth_connection_rejected_details": "<0>The application connected to the call authentication service at <2>{{url}}, but it responded with status code {{status}} ({{response}}). If you are the server admin, make sure <10>lk-jwt-service is listening at that address and check the logs for more information.", + "configuration_error": "Configuration error", + "configuration_error_description": "There is a configuration issues with the system. Please contact your administrator.", + "server_error": "Server error", + "server_error_description": "There is a server issue with the system. Please try again and contact your administrator if the problem persists.", + "network_error": "Network error", + "network_error_description": "There is a network issue with the system. Please check your network connection and try again. Alternatively try a different network if available.", "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.", "connection_failed": "Connection failed", diff --git a/src/RichError.tsx b/src/RichError.tsx index e5b01515..d48e3469 100644 --- a/src/RichError.tsx +++ b/src/RichError.tsx @@ -14,10 +14,11 @@ import { } from "react"; import { Trans, useTranslation } from "react-i18next"; import { + ErrorIcon, OfflineIcon, PopOutIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { Button, Link } from "@vector-im/compound-web"; +import { Button } from "@vector-im/compound-web"; import { ErrorView } from "./ErrorView"; @@ -58,40 +59,22 @@ export class OpenElsewhereError extends RichError { } } -interface AuthConnectionFailedProps { - livekitServiceUrl: string; +interface ConfigurationErrorViewProps { + children?: ReactNode; } -const AuthConnectionFailed: FC = ({ - livekitServiceUrl, +const ConfigurationErrorView: FC = ({ + children, }) => { const { t } = useTranslation(); const [showDetails, setShowDetails] = useState(false); const onShowDetailsClick = useCallback(() => setShowDetails(true), []); return ( - -

{t("error.connection_failed_description")}

+ +

{t("error.configuration_error_description")}

{showDetails ? ( - -

- The application could not reach the call authentication service at{" "} - - {{ url: livekitServiceUrl } as unknown as ReactElement} - - . If you are the server admin, check the network logs and make sure{" "} - - lk-jwt-service - {" "} - is listening at that address. -

-
+ children ) : ( + )} +
+ ); +}; + +interface ServerErrorViewProps { + children?: ReactNode; +} + +const ServerErrorView: FC = ({ children }) => { + const { t } = useTranslation(); + const [showDetails, setShowDetails] = useState(false); + const onShowDetailsClick = useCallback(() => setShowDetails(true), []); + + return ( + +

{t("error.server_error_description")}

+ {showDetails ? ( + children + ) : ( + + )} +
+ ); +}; + +export class ConfigurationError extends RichError { + public constructor(message: string, richMessage: ReactNode, cause?: unknown) { super( - `Failed to connect to ${livekitServiceUrl}`, - , + message, + {richMessage}, cause, ); } } -interface AuthConnectionRejectedProps { - livekitServiceUrl: string; - status: number; - response: string; +export class NetworkError extends RichError { + public constructor(message: string, richMessage: ReactNode, cause?: unknown) { + super(message, {richMessage}, cause); + } } -const AuthConnectionRejected: FC = ({ - livekitServiceUrl, - status, - response, -}) => { - const { t } = useTranslation(); - const [showDetails, setShowDetails] = useState(false); - const onShowDetailsClick = useCallback(() => setShowDetails(true), []); +export class ServerError extends RichError { + public constructor(message: string, richMessage: ReactNode, cause?: unknown) { + super(message, {richMessage}, cause); + } +} - return ( - -

{t("error.connection_rejected_description")}

- {showDetails ? ( - -

- The application connected to the call authentication service at{" "} - - {{ url: livekitServiceUrl } as unknown as ReactElement} - - , but it responded with status code{" "} - {{ status } as unknown as ReactElement} ( - {{ response } as unknown as ReactElement}). If you are the server - admin, make sure{" "} - - lk-jwt-service - {" "} - is listening at that address and check the logs for more - information. -

-
- ) : ( - - )} -
- ); -}; - -export class AuthConnectionRejectedError extends RichError { - public constructor( - livekitServiceUrl: string, - status: number, - response: string, - ) { +export class URLBuildingConfigurationError extends ConfigurationError { + public constructor(baseUrl: string, cause?: unknown) { + let message: string; + if (cause instanceof Error) { + message = cause.message; + } else { + message = "Unknown error"; + } super( - `Failed to connect to ${livekitServiceUrl} (status ${status})`, - , + `Unable to build URL based on: ${baseUrl}`, + +

+ The URL derived from{" "} + {{ baseUrl } as unknown as ReactElement} is not valid:{" "} +

{{ message } as unknown as ReactElement}
+

+
, + cause, + ); + } +} + +export class ResourceNotFoundConfigurationError extends ConfigurationError { + public constructor(url: URL) { + super( + `The server returned a 404 response for: ${url.href}`, + +

+ The request to{" "} + {{ url: url.href } as unknown as ReactElement} returned a{" "} + 404 response. +

+
, + ); + } +} + +export class UnexpectedResponseCodeError extends ServerError { + public constructor(url: URL, status: number, response: string) { + super( + `Received unexpected response code from ${url.href}: ${status}`, + +

+ The application received an unexpected response from{" "} + {{ url } as unknown as ReactElement}. It received status + code {{ status } as unknown as ReactElement}:{" "} +

{{ response } as unknown as ReactElement}
. +

+
, + ); + } +} + +export class FetchError extends ServerError { + public constructor(url: URL, cause: unknown) { + let message: string; + if (cause instanceof Error) { + message = cause.message; + } else { + message = "Unknown error"; + } + + super( + `Failed to connect to ${url.href}: ${message}`, + +

+ The application received an unexpected response from{" "} + {{ url: url.href } as unknown as ReactElement}. It + received status code{" "} + {{ message } as unknown as ReactElement}. +

+
, + ); + } +} + +export class InvalidServerResponseError extends ServerError { + public constructor(url: URL, cause: unknown) { + let message: string; + if (cause instanceof Error) { + message = cause.message; + } else { + message = "Unknown error"; + } + + super( + `Invalid response received from ${url.href}: ${message}`, + +

+ The server at{" "} + {{ url: url.href } as unknown as ReactElement} returned + an invalid response:{" "} +

{{ message } as unknown as ReactElement}
+

+
, ); } } diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index da6e5777..b88499dc 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -13,8 +13,11 @@ import { type LivekitFocus } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; import { useActiveLivekitFocus } from "../room/useActiveFocus"; import { - AuthConnectionFailedError, - AuthConnectionRejectedError, + FetchError, + InvalidServerResponseError, + ResourceNotFoundConfigurationError, + UnexpectedResponseCodeError, + URLBuildingConfigurationError, } from "../RichError"; export interface SFUConfig { @@ -87,9 +90,18 @@ async function getLiveKitJWT( roomName: string, openIDToken: IOpenIDToken, ): Promise { + let url: URL; + + try { + // TODO: check that relative URLs are handled as expected by this + url = new URL("sfu/get", livekitServiceURL); + } catch (e) { + throw new URLBuildingConfigurationError(livekitServiceURL, e); + } + let res: Response; try { - res = await fetch(livekitServiceURL + "/sfu/get", { + res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", @@ -101,16 +113,17 @@ async function getLiveKitJWT( }), }); } catch (e) { - throw new AuthConnectionFailedError(livekitServiceURL, e); + throw new FetchError(url, e); } if (!res.ok) { throw res.status === 404 - ? new AuthConnectionFailedError(livekitServiceURL) - : new AuthConnectionRejectedError( - livekitServiceURL, - res.status, - await res.text(), - ); + ? new ResourceNotFoundConfigurationError(url) + : new UnexpectedResponseCodeError(url, res.status, await res.text()); + } + + try { + return await res.json(); + } catch (e) { + throw new InvalidServerResponseError(url, e); } - return await res.json(); }