diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx index feda413..0459715 100644 --- a/src/app/routes/__root.tsx +++ b/src/app/routes/__root.tsx @@ -65,7 +65,9 @@ export const Route = createRootRouteWithContext<{ errorComponent: (props) => { return ( - + + + ); }, diff --git a/src/app/routes/refresh-session.tsx b/src/app/routes/refresh-session.tsx index 8ab33a8..47bc840 100644 --- a/src/app/routes/refresh-session.tsx +++ b/src/app/routes/refresh-session.tsx @@ -18,10 +18,21 @@ function RouteComponent() { const urlParams = new URLSearchParams(window.location.search) const redirect = urlParams.get('redirect') - if (redirect && !redirect.startsWith('/_serverFn')) { + const isServerFunction = redirect && ( + redirect.startsWith('_serverFn') || + redirect.startsWith('api/') || + redirect.includes('_serverFn') + ); + + if (redirect && !isServerFunction) { window.location.href = decodeURIComponent(redirect) } else { - window.location.href = '/' + const referrer = document.referrer; + const referrerUrl = referrer && !referrer.includes('/_serverFn') && !referrer.includes('/api/') + ? referrer + : '/'; + + window.location.href = referrerUrl; } } else { window.location.href = '/login' diff --git a/src/features/players/queries.ts b/src/features/players/queries.ts index 730896a..c73a930 100644 --- a/src/features/players/queries.ts +++ b/src/features/players/queries.ts @@ -34,7 +34,18 @@ export const useMe = () => { queryFn, options: { staleTime: 0, - refetchOnMount: true + refetchOnMount: true, + retry: (failureCount, error: any) => { + if (error?.response?.status === 401) { + const errorData = error?.response?.data; + if (errorData?.error === "SESSION_REFRESH_REQUIRED") { + const currentUrl = window.location.pathname + window.location.search; + window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`; + return false; + } + } + return failureCount < 3; + } } }); }; diff --git a/src/features/players/server.ts b/src/features/players/server.ts index ec2f2ee..7edb775 100644 --- a/src/features/players/server.ts +++ b/src/features/players/server.ts @@ -23,8 +23,7 @@ export const fetchMe = createServerFn() metadata: context.metadata }; } catch (error: any) { - // If getSessionContext throws (unauthenticated or redirect), return empty state - logger.info('fetchMe: No authenticated user or redirect needed'); + logger.info('fetchMe: Session error', error.message); return { user: undefined, roles: [], metadata: {} }; } }) diff --git a/src/lib/tanstack-query/hooks/use-server-mutation.ts b/src/lib/tanstack-query/hooks/use-server-mutation.ts index d04fe27..a6e3ccf 100644 --- a/src/lib/tanstack-query/hooks/use-server-mutation.ts +++ b/src/lib/tanstack-query/hooks/use-server-mutation.ts @@ -23,16 +23,34 @@ export function useServerMutation( return useMutation({ ...mutationOptions, mutationFn: async (variables: TVariables) => { - const result = await mutationFn(variables); - - if (!result.success) { - if (showErrorToast) { - toast.error(result.error.userMessage); + try { + const result = await mutationFn(variables); + + if (!result.success) { + if (showErrorToast) { + toast.error(result.error.userMessage); + } + throw new Error(result.error.userMessage); } - throw new Error(result.error.userMessage); + + return result.data; + } catch (error: any) { + if (error?.response?.status === 401) { + try { + const errorData = typeof error.response.data === 'string' + ? JSON.parse(error.response.data) + : error.response.data; + + if (errorData?.error === "SESSION_REFRESH_REQUIRED") { + const currentUrl = window.location.pathname + window.location.search; + window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`; + throw new Error("SESSION_REFRESH_REQUIRED"); + } + } catch (parseError) {} + } + + throw error; } - - return result.data; }, onSuccess: (data, variables, context) => { if (showSuccessToast && successMessage) { diff --git a/src/utils/supertokens.ts b/src/utils/supertokens.ts index 13d2509..5842f66 100644 --- a/src/utils/supertokens.ts +++ b/src/utils/supertokens.ts @@ -70,10 +70,14 @@ export const verifySuperTokensSession = async ( }; }; -export const getSessionContext = async (request: Request): Promise => { +export const getSessionContext = async (request: Request, options?: { isServerFunction?: boolean }): Promise => { const session = await verifySuperTokensSession(request); if (session.context.session?.tryRefresh) { + if (options?.isServerFunction) { + throw new Error("SESSION_REFRESH_REQUIRED"); + } + const url = new URL(request.url); const from = encodeURIComponent(url.pathname + url.search); throw redirect({ @@ -107,22 +111,56 @@ export const superTokensFunctionMiddleware = createMiddleware({ type: "function", }).server(async ({ next, response }) => { const request = getWebRequest(); - const context = await getSessionContext(request); - return next({ context }); + + try { + const context = await getSessionContext(request, { isServerFunction: true }); + return next({ context }); + } catch (error: any) { + if (error.message === "SESSION_REFRESH_REQUIRED") { + throw new Response( + JSON.stringify({ + error: "SESSION_REFRESH_REQUIRED", + message: "Session needs to be refreshed" + }), + { + status: 401, + headers: { "Content-Type": "application/json" } + } + ); + } + throw error; + } }); export const superTokensAdminFunctionMiddleware = createMiddleware({ type: "function", }).server(async ({ next }) => { const request = getWebRequest(); - const context = await getSessionContext(request); + + try { + const context = await getSessionContext(request, { isServerFunction: true }); - if (context.roles?.includes("Admin")) { - return next({ context }); + if (context.roles?.includes("Admin")) { + return next({ context }); + } + + logger.error("Unauthorized user in admin function.", context); + throw new Error("Unauthorized"); + } catch (error: any) { + if (error.message === "SESSION_REFRESH_REQUIRED") { + throw new Response( + JSON.stringify({ + error: "SESSION_REFRESH_REQUIRED", + message: "Session needs to be refreshed" + }), + { + status: 401, + headers: { "Content-Type": "application/json" } + } + ); + } + throw error; } - - logger.error("Unauthorized user in admin function.", context); - throw new Error("Unauthorized"); }); export const fetchUserRoles = async (userAuthId: string) => {