From 2e49d2db8b0eb008f9b4adb139c7f3e6bec9003b Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 25 Apr 2022 12:38:10 -0400 Subject: [PATCH] Rename ConnectedRoute component --- ...nectedRoute.tsx => ConnectionRequired.tsx} | 65 ++++++++++--------- src/components/HistoryRouter.tsx | 10 +++ src/routes/index.tsx | 6 +- 3 files changed, 48 insertions(+), 33 deletions(-) rename src/components/{ConnectedRoute.tsx => ConnectionRequired.tsx} (66%) diff --git a/src/components/ConnectedRoute.tsx b/src/components/ConnectionRequired.tsx similarity index 66% rename from src/components/ConnectedRoute.tsx rename to src/components/ConnectionRequired.tsx index ce5f639eba..1d127e2e89 100644 --- a/src/components/ConnectedRoute.tsx +++ b/src/components/ConnectionRequired.tsx @@ -1,13 +1,13 @@ import React, { FunctionComponent, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import globalize from '../scripts/globalize'; import alert from './alert'; import { appRouter } from './appRouter'; import loading from './loading/loading'; import ServerConnections from './ServerConnections'; +import globalize from '../scripts/globalize'; -enum Routes { +enum BounceRoutes { Home = '/home.html', Login = '/login.html', SelectServer = '/selectserver.html', @@ -22,33 +22,38 @@ enum ConnectionState { ServerUpdateNeeded = 'ServerUpdateNeeded' } -type ConnectedRouteProps = { - isAdminRoute?: boolean, - isUserRoute?: boolean, - roles?: string[] +type ConnectionRequiredProps = { + isAdminRequired?: boolean, + isUserRequired?: boolean }; -const ConnectedRoute: FunctionComponent = ({ +/** + * A component that ensures a server connection has been established. + * Additional parameters exist to verify a user or admin have authenticated. + * If a condition fails, this component will navigate to the appropriate page. + */ +const ConnectionRequired: FunctionComponent = ({ children, - isAdminRoute = false, - isUserRoute = true + isAdminRequired = false, + isUserRequired = true }) => { const navigate = useNavigate(); const [ isLoading, setIsLoading ] = useState(true); useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const bounce = async (connectionResponse: any) => { switch (connectionResponse.State) { case ConnectionState.SignedIn: // Already logged in, bounce to the home page - console.debug('[ConnectedRoute] already logged in, redirecting to home'); - navigate(Routes.Home); + console.debug('[ConnectionRequired] already logged in, redirecting to home'); + navigate(BounceRoutes.Home); return; case ConnectionState.ServerSignIn: // Bounce to the login page - console.debug('[ConnectedRoute] not logged in, redirecting to login page'); - navigate(Routes.Login, { + console.debug('[ConnectionRequired] not logged in, redirecting to login page'); + navigate(BounceRoutes.Login, { state: { serverid: connectionResponse.ApiClient.serverId() } @@ -56,8 +61,8 @@ const ConnectedRoute: FunctionComponent = ({ return; case ConnectionState.ServerSelection: // Bounce to select server page - console.debug('[ConnectedRoute] redirecting to select server page'); - navigate(Routes.SelectServer); + console.debug('[ConnectionRequired] redirecting to select server page'); + navigate(BounceRoutes.SelectServer); return; case ConnectionState.ServerUpdateNeeded: // Show update needed message and bounce to select server page @@ -67,14 +72,14 @@ const ConnectedRoute: FunctionComponent = ({ html: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') }); } catch (ex) { - console.warn('[ConnectedRoute] failed to show alert', ex); + console.warn('[ConnectionRequired] failed to show alert', ex); } - console.debug('[ConnectedRoute] server update required, redirecting to select server page'); - navigate(Routes.SelectServer); + console.debug('[ConnectionRequired] server update required, redirecting to select server page'); + navigate(BounceRoutes.SelectServer); return; } - console.warn('[ConnectedRoute] unhandled connection state', connectionResponse.State); + console.warn('[ConnectionRequired] unhandled connection state', connectionResponse.State); }; const validateConnection = async () => { @@ -93,12 +98,12 @@ const ConnectedRoute: FunctionComponent = ({ const systemInfo = await infoResponse.json(); if (!systemInfo.StartupWizardCompleted) { // Bounce to the wizard - console.info('[ConnectedRoute] startup wizard is not complete, redirecting there'); - navigate(Routes.StartWizard); + console.info('[ConnectionRequired] startup wizard is not complete, redirecting there'); + navigate(BounceRoutes.StartWizard); return; } } catch (ex) { - console.error('[ConnectedRoute] checking wizard status failed', ex); + console.error('[ConnectionRequired] checking wizard status failed', ex); } } @@ -112,27 +117,27 @@ const ConnectedRoute: FunctionComponent = ({ const client = ServerConnections.currentApiClient(); // If this is a user route, ensure a user is logged in - if ((isAdminRoute || isUserRoute) && !client?.isLoggedIn()) { + if ((isAdminRequired || isUserRequired) && !client?.isLoggedIn()) { try { - console.warn('[ConnectedRoute] unauthenticated user attempted to access user route'); + console.warn('[ConnectionRequired] unauthenticated user attempted to access user route'); bounce(await ServerConnections.connect()); } catch (ex) { - console.warn('[ConnectedRoute] error bouncing from user route', ex); + console.warn('[ConnectionRequired] error bouncing from user route', ex); } return; } // If this is an admin route, ensure the user has access - if (isAdminRoute) { + if (isAdminRequired) { try { const user = await client.getCurrentUser(); if (!user.Policy.IsAdministrator) { - console.warn('[ConnectedRoute] normal user attempted to access admin route'); + console.warn('[ConnectionRequired] normal user attempted to access admin route'); bounce(await ServerConnections.connect()); return; } } catch (ex) { - console.warn('[ConnectedRoute] error bouncing from admin route', ex); + console.warn('[ConnectionRequired] error bouncing from admin route', ex); return; } } @@ -142,7 +147,7 @@ const ConnectedRoute: FunctionComponent = ({ loading.show(); validateConnection(); - }, [ isAdminRoute, isUserRoute, navigate ]); + }, [ isAdminRequired, isUserRequired, navigate ]); useEffect(() => { if (!isLoading) { @@ -159,4 +164,4 @@ const ConnectedRoute: FunctionComponent = ({ ); }; -export default ConnectedRoute; +export default ConnectionRequired; diff --git a/src/components/HistoryRouter.tsx b/src/components/HistoryRouter.tsx index 9b0e64f60a..68577dc1d5 100644 --- a/src/components/HistoryRouter.tsx +++ b/src/components/HistoryRouter.tsx @@ -2,8 +2,16 @@ import React, { useLayoutEffect } from 'react'; import { HistoryRouterProps, Router } from 'react-router-dom'; import { Update } from 'history'; +/** Strips leading "!" from paths */ const normalizePath = (pathname: string) => pathname.replace(/^!/, ''); +/** + * A slightly customized version of the HistoryRouter from react-router-dom. + * We need to use HistoryRouter to have a shared history state between react-router and appRouter, but it does not seem + * to be properly exported in the upstream package. + * We also needed some customizations to handle #! routes. + * Refs: https://github.com/remix-run/react-router/blob/v6.3.0/packages/react-router-dom/index.tsx#L222 + */ export function HistoryRouter({ basename, children, history }: HistoryRouterProps) { const [state, setState] = React.useState({ action: history.action, @@ -13,6 +21,7 @@ export function HistoryRouter({ basename, children, history }: HistoryRouterProp useLayoutEffect(() => { const onHistoryChange = (update: Update) => { if (update.location.pathname.startsWith('!')) { + // When the location changes, we need to check for #! paths and replace the location with the "!" stripped history.replace(normalizePath(update.location.pathname), update.location.state); } else { setState(update); @@ -29,6 +38,7 @@ export function HistoryRouter({ basename, children, history }: HistoryRouterProp children={children} location={{ ...state.location, + // The original location does not get replaced with the normalized version, so we need to strip it here pathname: normalizePath(state.location.pathname) }} navigationType={state.action} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 49c8e52a49..3cc4896874 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Route, Routes } from 'react-router-dom'; -import ConnectedRoute from '../components/ConnectedRoute'; +import ConnectionRequired from '../components/ConnectionRequired'; import SearchPage from './search'; const AppRoutes = () => ( @@ -10,9 +10,9 @@ const AppRoutes = () => ( + - + } /> {/* Suppress warnings for unhandled routes */}