1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Rename ConnectedRoute component

This commit is contained in:
Bill Thornton 2022-04-25 12:38:10 -04:00
parent 05dbeff473
commit 2e49d2db8b
3 changed files with 48 additions and 33 deletions

View file

@ -1,13 +1,13 @@
import React, { FunctionComponent, useEffect, useState } from 'react'; import React, { FunctionComponent, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import globalize from '../scripts/globalize';
import alert from './alert'; import alert from './alert';
import { appRouter } from './appRouter'; import { appRouter } from './appRouter';
import loading from './loading/loading'; import loading from './loading/loading';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import globalize from '../scripts/globalize';
enum Routes { enum BounceRoutes {
Home = '/home.html', Home = '/home.html',
Login = '/login.html', Login = '/login.html',
SelectServer = '/selectserver.html', SelectServer = '/selectserver.html',
@ -22,33 +22,38 @@ enum ConnectionState {
ServerUpdateNeeded = 'ServerUpdateNeeded' ServerUpdateNeeded = 'ServerUpdateNeeded'
} }
type ConnectedRouteProps = { type ConnectionRequiredProps = {
isAdminRoute?: boolean, isAdminRequired?: boolean,
isUserRoute?: boolean, isUserRequired?: boolean
roles?: string[]
}; };
const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({ /**
* 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<ConnectionRequiredProps> = ({
children, children,
isAdminRoute = false, isAdminRequired = false,
isUserRoute = true isUserRequired = true
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [ isLoading, setIsLoading ] = useState(true); const [ isLoading, setIsLoading ] = useState(true);
useEffect(() => { useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const bounce = async (connectionResponse: any) => { const bounce = async (connectionResponse: any) => {
switch (connectionResponse.State) { switch (connectionResponse.State) {
case ConnectionState.SignedIn: case ConnectionState.SignedIn:
// Already logged in, bounce to the home page // Already logged in, bounce to the home page
console.debug('[ConnectedRoute] already logged in, redirecting to home'); console.debug('[ConnectionRequired] already logged in, redirecting to home');
navigate(Routes.Home); navigate(BounceRoutes.Home);
return; return;
case ConnectionState.ServerSignIn: case ConnectionState.ServerSignIn:
// Bounce to the login page // Bounce to the login page
console.debug('[ConnectedRoute] not logged in, redirecting to login page'); console.debug('[ConnectionRequired] not logged in, redirecting to login page');
navigate(Routes.Login, { navigate(BounceRoutes.Login, {
state: { state: {
serverid: connectionResponse.ApiClient.serverId() serverid: connectionResponse.ApiClient.serverId()
} }
@ -56,8 +61,8 @@ const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
return; return;
case ConnectionState.ServerSelection: case ConnectionState.ServerSelection:
// Bounce to select server page // Bounce to select server page
console.debug('[ConnectedRoute] redirecting to select server page'); console.debug('[ConnectionRequired] redirecting to select server page');
navigate(Routes.SelectServer); navigate(BounceRoutes.SelectServer);
return; return;
case ConnectionState.ServerUpdateNeeded: case ConnectionState.ServerUpdateNeeded:
// Show update needed message and bounce to select server page // Show update needed message and bounce to select server page
@ -67,14 +72,14 @@ const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
html: globalize.translate('ServerUpdateNeeded', '<a href="https://github.com/jellyfin/jellyfin">https://github.com/jellyfin/jellyfin</a>') html: globalize.translate('ServerUpdateNeeded', '<a href="https://github.com/jellyfin/jellyfin">https://github.com/jellyfin/jellyfin</a>')
}); });
} catch (ex) { } 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'); console.debug('[ConnectionRequired] server update required, redirecting to select server page');
navigate(Routes.SelectServer); navigate(BounceRoutes.SelectServer);
return; return;
} }
console.warn('[ConnectedRoute] unhandled connection state', connectionResponse.State); console.warn('[ConnectionRequired] unhandled connection state', connectionResponse.State);
}; };
const validateConnection = async () => { const validateConnection = async () => {
@ -93,12 +98,12 @@ const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
const systemInfo = await infoResponse.json(); const systemInfo = await infoResponse.json();
if (!systemInfo.StartupWizardCompleted) { if (!systemInfo.StartupWizardCompleted) {
// Bounce to the wizard // Bounce to the wizard
console.info('[ConnectedRoute] startup wizard is not complete, redirecting there'); console.info('[ConnectionRequired] startup wizard is not complete, redirecting there');
navigate(Routes.StartWizard); navigate(BounceRoutes.StartWizard);
return; return;
} }
} catch (ex) { } 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<ConnectedRouteProps> = ({
const client = ServerConnections.currentApiClient(); const client = ServerConnections.currentApiClient();
// If this is a user route, ensure a user is logged in // If this is a user route, ensure a user is logged in
if ((isAdminRoute || isUserRoute) && !client?.isLoggedIn()) { if ((isAdminRequired || isUserRequired) && !client?.isLoggedIn()) {
try { 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()); bounce(await ServerConnections.connect());
} catch (ex) { } catch (ex) {
console.warn('[ConnectedRoute] error bouncing from user route', ex); console.warn('[ConnectionRequired] error bouncing from user route', ex);
} }
return; return;
} }
// If this is an admin route, ensure the user has access // If this is an admin route, ensure the user has access
if (isAdminRoute) { if (isAdminRequired) {
try { try {
const user = await client.getCurrentUser(); const user = await client.getCurrentUser();
if (!user.Policy.IsAdministrator) { 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()); bounce(await ServerConnections.connect());
return; return;
} }
} catch (ex) { } catch (ex) {
console.warn('[ConnectedRoute] error bouncing from admin route', ex); console.warn('[ConnectionRequired] error bouncing from admin route', ex);
return; return;
} }
} }
@ -142,7 +147,7 @@ const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
loading.show(); loading.show();
validateConnection(); validateConnection();
}, [ isAdminRoute, isUserRoute, navigate ]); }, [ isAdminRequired, isUserRequired, navigate ]);
useEffect(() => { useEffect(() => {
if (!isLoading) { if (!isLoading) {
@ -159,4 +164,4 @@ const ConnectedRoute: FunctionComponent<ConnectedRouteProps> = ({
); );
}; };
export default ConnectedRoute; export default ConnectionRequired;

View file

@ -2,8 +2,16 @@ import React, { useLayoutEffect } from 'react';
import { HistoryRouterProps, Router } from 'react-router-dom'; import { HistoryRouterProps, Router } from 'react-router-dom';
import { Update } from 'history'; import { Update } from 'history';
/** Strips leading "!" from paths */
const normalizePath = (pathname: string) => pathname.replace(/^!/, ''); 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) { export function HistoryRouter({ basename, children, history }: HistoryRouterProps) {
const [state, setState] = React.useState({ const [state, setState] = React.useState({
action: history.action, action: history.action,
@ -13,6 +21,7 @@ export function HistoryRouter({ basename, children, history }: HistoryRouterProp
useLayoutEffect(() => { useLayoutEffect(() => {
const onHistoryChange = (update: Update) => { const onHistoryChange = (update: Update) => {
if (update.location.pathname.startsWith('!')) { 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); history.replace(normalizePath(update.location.pathname), update.location.state);
} else { } else {
setState(update); setState(update);
@ -29,6 +38,7 @@ export function HistoryRouter({ basename, children, history }: HistoryRouterProp
children={children} children={children}
location={{ location={{
...state.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) pathname: normalizePath(state.location.pathname)
}} }}
navigationType={state.action} navigationType={state.action}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import ConnectedRoute from '../components/ConnectedRoute'; import ConnectionRequired from '../components/ConnectionRequired';
import SearchPage from './search'; import SearchPage from './search';
const AppRoutes = () => ( const AppRoutes = () => (
@ -10,9 +10,9 @@ const AppRoutes = () => (
<Route <Route
path='search.html' path='search.html'
element={ element={
<ConnectedRoute> <ConnectionRequired>
<SearchPage /> <SearchPage />
</ConnectedRoute> </ConnectionRequired>
} }
/> />
{/* Suppress warnings for unhandled routes */} {/* Suppress warnings for unhandled routes */}