Skip to content

Commit

Permalink
Implemented Hook and Helper function for redirects
Browse files Browse the repository at this point in the history
This navigate() and window.location.assign() replacement maintains
x-ms-routing-name parameter in the query string, and can be used for
other query parameters later if need be.
  • Loading branch information
fmaddenflx committed Feb 11, 2025
1 parent fb17436 commit 80f0c05
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 14 deletions.
6 changes: 3 additions & 3 deletions user-interface/src/lib/components/GoHome.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LOGIN_SUCCESS_PATH } from '@/login/login-library';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useCamsNavigator from '../hooks/UseCamsNavigator';

export type GoHomeProps = {
path?: string;
Expand All @@ -17,9 +17,9 @@ export type GoHomeProps = {
* @returns
*/
export function GoHome(props: GoHomeProps) {
const navigate = useNavigate();
const navigator = useCamsNavigator();
useEffect(() => {
navigate(props.path ?? LOGIN_SUCCESS_PATH);
navigator.navigateTo(props.path ?? LOGIN_SUCCESS_PATH);
}, []);
return <></>;
}
47 changes: 47 additions & 0 deletions user-interface/src/lib/hooks/UseCamsNavigator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useLocation, useNavigate, Location } from 'react-router-dom';

function getFinalDestination(
destination: string,
location: globalThis.Location | Location = window.location,
) {
let qParams: string = '';
const msRoutingName = 'x-ms-routing-name';
if (location.search.includes(msRoutingName)) {

Check failure on line 9 in user-interface/src/lib/hooks/UseCamsNavigator.ts

View workflow job for this annotation

GitHub Actions / unit-test-frontend / Unit test user-interface

src/login/broadcast-logout.test.ts > Broadcast Logout > should handle broadcast-logout properly

TypeError: Cannot read properties of undefined (reading 'includes') ❯ getFinalDestination src/lib/hooks/UseCamsNavigator.ts:9:23 ❯ Module.redirectTo src/lib/hooks/UseCamsNavigator.ts:32:26 ❯ handleLogoutBroadcast src/login/broadcast-logout.ts:10:3 ❯ src/login/broadcast-logout.test.ts:44:5

Check failure on line 9 in user-interface/src/lib/hooks/UseCamsNavigator.ts

View workflow job for this annotation

GitHub Actions / unit-test-frontend / Unit test user-interface

src/login/broadcast-logout.test.ts > Broadcast Logout > should handle broadcast-logout properly

TypeError: Cannot read properties of undefined (reading 'includes') ❯ getFinalDestination src/lib/hooks/UseCamsNavigator.ts:9:23 ❯ Module.redirectTo src/lib/hooks/UseCamsNavigator.ts:32:26 ❯ handleLogoutBroadcast src/login/broadcast-logout.ts:10:3 ❯ src/login/broadcast-logout.test.ts:44:5
const query: Record<string, string> = location.search
.substring(1)
.split('&')
.reduce<Record<string, string>>(
(acc, item) => {
const [key, val] = item.split('=');
if (key && val) acc[key] = val;
return acc;
},
{} as Record<string, string>,
);
if (query[msRoutingName]) {
qParams += `?${msRoutingName}=${query[msRoutingName]}`;
}
}
return destination + qParams;
}

export const redirectTo = (
destination: string,
location: globalThis.Location | Location = window.location,
) => {
window.location.assign(getFinalDestination(destination, location));
};

export default function useCamsNavigator() {
const location = useLocation();
const navigate = useNavigate();

const navigateTo = (destination: string) => {
navigate(getFinalDestination(destination, location));
};

return {
navigateTo,
redirectTo,
};
}
5 changes: 4 additions & 1 deletion user-interface/src/login/AccessDenied.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Alert, { UswdsAlertStyle } from '@/lib/components/uswds/Alert';
import { BlankPage } from './BlankPage';
import { LOGIN_PATH } from './login-library';
import Button from '@/lib/components/uswds/Button';
import useCamsNavigator from '@/lib/hooks/UseCamsNavigator';

const DEFAULT_MESSAGE = 'Access to this application is denied without successful authentication.';

Expand All @@ -10,10 +11,12 @@ export type AccessDeniedProps = {
};

export function AccessDenied(props: AccessDeniedProps) {
const navigator = useCamsNavigator();

function handleLoginRedirect() {
const { host, protocol } = window.location;
const loginUri = protocol + '//' + host + LOGIN_PATH;
window.location.assign(loginUri);
navigator.redirectTo(loginUri);
}

return (
Expand Down
9 changes: 6 additions & 3 deletions user-interface/src/login/Session.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { PropsWithChildren, useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import { LOGIN_PATHS, LOGIN_BASE_PATH } from './login-library';
import { LocalStorage } from '@/lib/utils/local-storage';
import Api2 from '@/lib/models/api2';
import { AccessDenied } from './AccessDenied';
import { Interstitial } from './Interstitial';
import { CamsSession } from '@common/cams/session';
import { CamsUser } from '@common/cams/users';
import useCamsNavigator from '@/lib/hooks/UseCamsNavigator';

type SessionState = {
isLoaded: boolean;
Expand Down Expand Up @@ -59,7 +60,7 @@ export type SessionProps = Omit<CamsSession, 'user'> & PropsWithChildren & { use
export function Session(props: SessionProps) {
const { accessToken, provider, expires, issuer } = props;
const user = props.user ?? { id: '', name: '' };
const navigate = useNavigate();
const navigator = useCamsNavigator();
const location = useLocation();
const { state, actions } = useStateAndActions();

Expand All @@ -70,7 +71,9 @@ export function Session(props: SessionProps) {
}, []);

useEffect(() => {
if (LOGIN_PATHS.includes(location.pathname)) navigate(LOGIN_BASE_PATH);
if (LOGIN_PATHS.includes(location.pathname)) {
navigator.navigateTo(LOGIN_BASE_PATH);
}
}, [state.isLoaded === true && !state.isError]);

if (!state.isLoaded) {
Expand Down
9 changes: 5 additions & 4 deletions user-interface/src/login/SessionEnd.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import Alert, { UswdsAlertStyle } from '@/lib/components/uswds/Alert';
import Button from '@/lib/components/uswds/Button';
import { LocalStorage } from '@/lib/utils/local-storage';
import { LOGIN_PATH, LOGOUT_SESSION_END_PATH } from './login-library';
import { BlankPage } from './BlankPage';
import { broadcastLogout } from '@/login/broadcast-logout';
import useCamsNavigator from '@/lib/hooks/UseCamsNavigator';

export function SessionEnd() {
const location = useLocation();
const navigate = useNavigate();

const navigator = useCamsNavigator();
function handleLoginRedirect() {
navigate(LOGIN_PATH);
navigator.navigateTo(LOGIN_PATH);
}

LocalStorage.removeSession();
Expand All @@ -21,7 +22,7 @@ export function SessionEnd() {

useEffect(() => {
if (location.pathname !== LOGOUT_SESSION_END_PATH) {
navigate(LOGOUT_SESSION_END_PATH);
navigator.navigateTo(LOGOUT_SESSION_END_PATH);
}
}, []);

Expand Down
3 changes: 2 additions & 1 deletion user-interface/src/login/broadcast-logout.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { redirectTo } from '@/lib/hooks/UseCamsNavigator';
import { BroadcastChannelHumble } from '@/lib/humble/broadcast-channel-humble';
import { LOGOUT_PATH } from '@/login/login-library';

Expand All @@ -6,7 +7,7 @@ let channel: BroadcastChannelHumble;
export function handleLogoutBroadcast() {
const { host, protocol } = window.location;
const logoutUri = protocol + '//' + host + LOGOUT_PATH;
window.location.assign(logoutUri);
redirectTo(logoutUri);
channel?.close();
}

Expand Down
3 changes: 2 additions & 1 deletion user-interface/src/login/http401-logout.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { isCamsApi } from '@/configuration/apiConfiguration';
import { LOGOUT_PATH } from './login-library';
import { redirectTo } from '@/lib/hooks/UseCamsNavigator';

export async function http401Hook(response: Response) {
if (response.status === 401 && isCamsApi(response.url)) {
const { host, protocol } = window.location;
const logoutUri = protocol + '//' + host + LOGOUT_PATH;
window.location.assign(logoutUri);
redirectTo(logoutUri);
}
}
3 changes: 2 additions & 1 deletion user-interface/src/login/inactive-logout.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import LocalStorage from '@/lib/utils/local-storage';
import { LOGOUT_PATH } from './login-library';
import { redirectTo } from '@/lib/hooks/UseCamsNavigator';

const POLLING_INTERVAL = 60000; // milliseconds
const TIMEOUT_MINUTES = import.meta.env['CAMS_INACTIVE_TIMEOUT'] ?? 30;
Expand All @@ -18,7 +19,7 @@ export function checkForInactivity() {

const { host, protocol } = window.location;
const logoutUri = protocol + '//' + host + LOGOUT_PATH;
window.location.assign(logoutUri);
redirectTo(logoutUri);
}
}

Expand Down

0 comments on commit 80f0c05

Please sign in to comment.