diff --git a/packages/console/src/components/SessionExpired/index.tsx b/packages/console/src/components/SessionExpired/index.tsx index 03bb1bc67..9e243b0a2 100644 --- a/packages/console/src/components/SessionExpired/index.tsx +++ b/packages/console/src/components/SessionExpired/index.tsx @@ -1,8 +1,7 @@ import { useLogto } from '@logto/react'; -import { useContext, useEffect } from 'react'; +import { useEffect } from 'react'; -import { getCallbackUrl } from '@/consts'; -import { TenantsContext } from '@/contexts/TenantsProvider'; +import useRedirectUri from '@/hooks/use-redirect-uri'; import { saveRedirect } from '@/utils/storage'; import AppLoading from '../AppLoading'; @@ -10,14 +9,14 @@ import AppLoading from '../AppLoading'; /** This component shows a loading indicator and tries to sign in again. */ function SessionExpired() { const { signIn, isLoading } = useLogto(); - const { currentTenantId } = useContext(TenantsContext); + const redirectUri = useRedirectUri(); useEffect(() => { if (!isLoading) { saveRedirect(); - void signIn(getCallbackUrl(currentTenantId).href); + void signIn(redirectUri.href); } - }, [signIn, isLoading, currentTenantId]); + }, [signIn, isLoading, redirectUri]); return ; } diff --git a/packages/console/src/consts/tenants.ts b/packages/console/src/consts/tenants.ts index 1e00e9a07..e7430d377 100644 --- a/packages/console/src/consts/tenants.ts +++ b/packages/console/src/consts/tenants.ts @@ -1,6 +1,3 @@ -import { ossConsolePath } from '@logto/schemas'; -import { conditionalArray } from '@silverhand/essentials'; - import { adminEndpoint, isCloud } from './env'; const getAdminTenantEndpoint = () => { @@ -17,12 +14,3 @@ const getAdminTenantEndpoint = () => { export const adminTenantEndpoint = getAdminTenantEndpoint(); export const mainTitle = isCloud ? 'Logto Cloud' : 'Logto Console'; - -export const getCallbackUrl = (tenantId?: string) => - new URL( - // Only Cloud has tenantId in callback URL - '/' + conditionalArray(isCloud ? tenantId : 'console', 'callback').join('/'), - window.location.origin - ); - -export const getSignOutRedirectPathname = () => (isCloud ? '/' : ossConsolePath); diff --git a/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx index c3a3a8639..7dd2a1c25 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx +++ b/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx @@ -11,12 +11,12 @@ import Profile from '@/assets/icons/profile.svg'; import SignOut from '@/assets/icons/sign-out.svg'; import UserAvatar from '@/components/UserAvatar'; import UserInfoCard from '@/components/UserInfoCard'; -import { getSignOutRedirectPathname } from '@/consts'; import Divider from '@/ds-components/Divider'; import Dropdown, { DropdownItem } from '@/ds-components/Dropdown'; import Spacer from '@/ds-components/Spacer'; import { Ring as Spinner } from '@/ds-components/Spinner'; import useCurrentUser from '@/hooks/use-current-user'; +import useRedirectUri from '@/hooks/use-redirect-uri'; import useTenantPathname from '@/hooks/use-tenant-pathname'; import useUserPreferences from '@/hooks/use-user-preferences'; import { DynamicAppearanceMode } from '@/types/appearance-mode'; @@ -33,6 +33,7 @@ function UserInfo() { const { user, isLoading: isLoadingUser } = useCurrentUser(); const anchorRef = useRef(null); const [showDropdown, setShowDropdown] = useState(false); + const postSignOutRedirectUri = useRedirectUri('signOut'); const [isLoading, setIsLoading] = useState(false); const { @@ -128,7 +129,7 @@ function UserInfo() { return; } setIsLoading(true); - void signOut(new URL(getSignOutRedirectPathname(), window.location.origin).toString()); + void signOut(postSignOutRedirectUri.href); }} > {t('menu.sign_out')} diff --git a/packages/console/src/containers/ProtectedRoutes/index.tsx b/packages/console/src/containers/ProtectedRoutes/index.tsx index b2799306e..c2e7d5205 100644 --- a/packages/console/src/containers/ProtectedRoutes/index.tsx +++ b/packages/console/src/containers/ProtectedRoutes/index.tsx @@ -5,8 +5,9 @@ import { Outlet, useSearchParams } from 'react-router-dom'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; import AppLoading from '@/components/AppLoading'; -import { searchKeys, getCallbackUrl } from '@/consts'; +import { searchKeys } from '@/consts'; import { TenantsContext } from '@/contexts/TenantsProvider'; +import useRedirectUri from '@/hooks/use-redirect-uri'; import { saveRedirect } from '@/utils/storage'; /** @@ -32,15 +33,16 @@ export default function ProtectedRoutes() { const api = useCloudApi(); const [searchParameters] = useSearchParams(); const { isAuthenticated, isLoading, signIn } = useLogto(); - const { currentTenantId, isInitComplete, resetTenants } = useContext(TenantsContext); + const { isInitComplete, resetTenants } = useContext(TenantsContext); + const redirectUri = useRedirectUri(); useEffect(() => { if (!isLoading && !isAuthenticated) { saveRedirect(); const isSignUpMode = yes(searchParameters.get(searchKeys.signUp)); - void signIn(getCallbackUrl(currentTenantId).href, conditional(isSignUpMode && 'signUp')); + void signIn(redirectUri.href, conditional(isSignUpMode && 'signUp')); } - }, [currentTenantId, isAuthenticated, isLoading, searchParameters, signIn]); + }, [redirectUri, isAuthenticated, isLoading, searchParameters, signIn]); useEffect(() => { if (isAuthenticated && !isInitComplete) { diff --git a/packages/console/src/hooks/use-redirect-uri.ts b/packages/console/src/hooks/use-redirect-uri.ts new file mode 100644 index 000000000..25708075f --- /dev/null +++ b/packages/console/src/hooks/use-redirect-uri.ts @@ -0,0 +1,20 @@ +import { ossConsolePath } from '@logto/schemas'; +import { conditionalArray } from '@silverhand/essentials'; +import { useHref } from 'react-router-dom'; + +import { isCloud } from '@/consts/env'; + +/** + * The hook that returns the absolute URL for the sign-in or sign-out callback. + * The path is not related to react-router, which means the path will also include + * the basename of react-router if it's set. + */ +const useRedirectUri = (flow: 'signIn' | 'signOut' = 'signIn') => { + const path = useHref( + conditionalArray(!isCloud && ossConsolePath, flow === 'signIn' && '/callback').join('') + ); + + return new URL(path, window.location.origin); +}; + +export default useRedirectUri; diff --git a/packages/console/src/pages/Welcome/index.tsx b/packages/console/src/pages/Welcome/index.tsx index 051fc10e6..f2d920b8b 100644 --- a/packages/console/src/pages/Welcome/index.tsx +++ b/packages/console/src/pages/Welcome/index.tsx @@ -4,8 +4,8 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Logo from '@/assets/images/logo.svg'; -import { getCallbackUrl } from '@/consts'; import Button from '@/ds-components/Button'; +import useRedirectUri from '@/hooks/use-redirect-uri'; import useTenantPathname from '@/hooks/use-tenant-pathname'; import useTheme from '@/hooks/use-theme'; @@ -16,6 +16,7 @@ function Welcome() { const { navigate } = useTenantPathname(); const { isAuthenticated, signIn } = useLogto(); const theme = useTheme(); + const redirectUri = useRedirectUri(); useEffect(() => { // If authenticated, navigate to the console root page directly @@ -40,7 +41,7 @@ function Welcome() { type="branding" title="welcome.create_account" onClick={() => { - void signIn(getCallbackUrl().href); + void signIn(redirectUri.href); }} />