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:
parent
05dbeff473
commit
2e49d2db8b
3 changed files with 48 additions and 33 deletions
|
@ -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;
|
|
@ -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}
|
||||||
|
|
|
@ -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 */}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue