29920738dc
Landing on /verify often forced users to refresh and log in again. The culprit is a logout side effect triggered by a stale token: when a previous session's token pair is still in localStorage, boot queries use it, fail, and the failed token renewal reacts by logging the user out (onUnauthenticatedError clears the token pair). That logout fires while the loginToken exchange is running, so it can wipe the fresh session that was just stored. Fix: clear the stale token pair right before exchanging the loginToken (in useVerifyLogin, so both /verify and /verify-email are covered) — with no stale token to renew, the logout side effect never fires against the new session. Also removes the redundant clientConfig gate on the verify effect, stops that same logout side effect from redirecting users off /verify-email mid-verification, and always re-enables app redirects after loading the user. Note: opening a loginToken link now replaces an existing valid session instead of keeping it. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22573?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
144 lines
5.0 KiB
TypeScript
144 lines
5.0 KiB
TypeScript
import { InMemoryCache } from '@apollo/client';
|
|
import { useMemo, useRef } from 'react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
import { ApolloFactory, type Options } from '@/apollo/services/apollo.factory';
|
|
import { ONGOING_USER_CREATION_PATHS } from '@/auth/constants/OngoingUserCreationPaths';
|
|
import { currentUserState } from '@/auth/states/currentUserState';
|
|
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|
import { returnToPathState } from '@/auth/states/returnToPathState';
|
|
import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath';
|
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
|
import { appVersionState } from '@/client-config/states/appVersionState';
|
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
|
import { AppPath } from 'twenty-shared/types';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
|
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
|
|
|
export const useApolloFactory = (options: Partial<Options> = {}) => {
|
|
// oxlint-disable-next-line twenty/no-state-useref
|
|
const apolloRef = useRef<ApolloFactory | null>(null);
|
|
|
|
const navigate = useNavigate();
|
|
const setTokenPair = useSetAtomState(tokenPairState);
|
|
const [currentWorkspace, setCurrentWorkspace] = useAtomState(
|
|
currentWorkspaceState,
|
|
);
|
|
const appVersion = useAtomStateValue(appVersionState);
|
|
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useAtomState(
|
|
currentWorkspaceMemberState,
|
|
);
|
|
const setCurrentUser = useSetAtomState(currentUserState);
|
|
const setCurrentUserWorkspace = useSetAtomState(currentUserWorkspaceState);
|
|
|
|
const setReturnToPath = useSetAtomState(returnToPathState);
|
|
const location = useLocation();
|
|
// oxlint-disable-next-line twenty/no-state-useref
|
|
const locationRef = useRef(location);
|
|
locationRef.current = location;
|
|
|
|
const { enqueueErrorSnackBar } = useSnackBar();
|
|
|
|
const apolloClient = useMemo(() => {
|
|
apolloRef.current = new ApolloFactory({
|
|
uri: `${REACT_APP_SERVER_BASE_URL}/graphql`,
|
|
cache: new InMemoryCache({
|
|
typePolicies: {
|
|
RemoteTable: {
|
|
keyFields: ['name'],
|
|
},
|
|
},
|
|
}),
|
|
|
|
defaultOptions: {
|
|
watchQuery: {
|
|
fetchPolicy: 'cache-and-network',
|
|
},
|
|
},
|
|
devtools: { enabled: process.env.IS_DEBUG_MODE === 'true' },
|
|
currentWorkspaceMember: currentWorkspaceMember,
|
|
currentWorkspace: currentWorkspace,
|
|
appVersion,
|
|
onTokenPairChange: (tokenPair) => {
|
|
setTokenPair(tokenPair);
|
|
},
|
|
onUnauthenticatedError: () => {
|
|
setTokenPair(null);
|
|
setCurrentUser(null);
|
|
setCurrentWorkspaceMember(null);
|
|
setCurrentWorkspace(null);
|
|
setCurrentUserWorkspace(null);
|
|
if (
|
|
![...ONGOING_USER_CREATION_PATHS, AppPath.ResetPassword].some(
|
|
(path) => isMatchingLocation(locationRef.current, path),
|
|
)
|
|
) {
|
|
const path = `${locationRef.current.pathname}${locationRef.current.search}${locationRef.current.hash}`;
|
|
|
|
if (isValidReturnToPath(path)) {
|
|
setReturnToPath(path);
|
|
}
|
|
navigate(AppPath.SignInUp);
|
|
}
|
|
},
|
|
onAppVersionMismatch: (message) => {
|
|
enqueueErrorSnackBar({
|
|
message,
|
|
options: {
|
|
dedupeKey: 'app-version-mismatch',
|
|
},
|
|
});
|
|
},
|
|
onPayloadTooLarge: (message) => {
|
|
enqueueErrorSnackBar({
|
|
message,
|
|
options: {
|
|
dedupeKey: 'payload-too-large',
|
|
},
|
|
});
|
|
},
|
|
extraLinks: [],
|
|
isDebugMode: process.env.IS_DEBUG_MODE === 'true',
|
|
// Override options
|
|
...options,
|
|
});
|
|
|
|
return apolloRef.current.getClient();
|
|
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
setTokenPair,
|
|
setCurrentUser,
|
|
setCurrentWorkspaceMember,
|
|
setCurrentWorkspace,
|
|
setReturnToPath,
|
|
enqueueErrorSnackBar,
|
|
]);
|
|
|
|
useUpdateEffect(() => {
|
|
if (isDefined(apolloRef.current)) {
|
|
apolloRef.current.updateWorkspaceMember(currentWorkspaceMember);
|
|
}
|
|
}, [currentWorkspaceMember]);
|
|
|
|
useUpdateEffect(() => {
|
|
if (isDefined(apolloRef.current)) {
|
|
apolloRef.current.updateCurrentWorkspace(currentWorkspace);
|
|
}
|
|
}, [currentWorkspace]);
|
|
|
|
useUpdateEffect(() => {
|
|
if (isDefined(apolloRef.current)) {
|
|
apolloRef.current.updateAppVersion(appVersion);
|
|
}
|
|
}, [appVersion]);
|
|
|
|
return apolloClient;
|
|
};
|