diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx index 60b84fa..e8b4c02 100644 --- a/src/app/routes/__root.tsx +++ b/src/app/routes/__root.tsx @@ -4,7 +4,6 @@ import { Outlet, Scripts, createRootRouteWithContext, - redirect, } from "@tanstack/react-router"; import * as React from "react"; import { DefaultCatchBoundary } from "@/components/DefaultCatchBoundary"; @@ -133,15 +132,6 @@ export const Route = createRootRouteWithContext<{ throw error; } - if (error instanceof Response && error.status === 440) { - console.log('__root beforeLoad: Session needs refresh, redirecting'); - const from = encodeURIComponent(location.pathname + location.search); - throw redirect({ - to: "/refresh-session", - search: { redirect: from } - }); - } - console.error('__root beforeLoad error:', error); return {}; } @@ -152,6 +142,32 @@ export const Route = createRootRouteWithContext<{ function RootComponent() { React.useEffect(() => { ensureSuperTokensFrontend(); + + if (typeof window !== 'undefined') { + const cookies = document.cookie.split(';'); + const accessTokenCookies = cookies.filter(c => c.trim().startsWith('sAccessToken=')); + + if (accessTokenCookies.length > 1) { + console.warn(`[Root] Found ${accessTokenCookies.length} duplicate sAccessToken cookies - clearing all SuperTokens cookies`); + + const cookieNames = ['sAccessToken', 'sRefreshToken', 'sIdRefreshToken', 'sFrontToken']; + const cookieDomain = (window as any).__COOKIE_DOMAIN__ || undefined; + + cookieNames.forEach(name => { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + + if (cookieDomain) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${cookieDomain}`; + } + }); + + console.log('[Root] Cleared duplicate cookies - page will reload to establish fresh session'); + + setTimeout(() => { + window.location.reload(); + }, 100); + } + } }, []); return ( diff --git a/src/features/players/server.ts b/src/features/players/server.ts index 70167cd..7edc7f3 100644 --- a/src/features/players/server.ts +++ b/src/features/players/server.ts @@ -56,21 +56,8 @@ export const fetchMe = createServerFn() } if (error?.message === "SESSION_REFRESH_REQUIRED") { - logger.info("FetchMe: Session refresh required (server function)"); - throw new Response( - JSON.stringify({ - error: "SESSION_REFRESH_REQUIRED", - message: "Session needs to be refreshed", - shouldRetry: true - }), - { - status: 440, - headers: { - "Content-Type": "application/json", - "X-Session-Expired": "true" - } - } - ); + logger.info("FetchMe: Session refresh required - returning empty auth (client will handle cleanup)"); + return { user: undefined, roles: [], metadata: {}, phone: undefined }; } if (error?.message === "Unauthenticated") { diff --git a/src/lib/supertokens/recipes/start-session.ts b/src/lib/supertokens/recipes/start-session.ts index 66d7b5b..8f2a5f5 100644 --- a/src/lib/supertokens/recipes/start-session.ts +++ b/src/lib/supertokens/recipes/start-session.ts @@ -10,32 +10,14 @@ export async function getSessionForStart(request: Request, options?: { sessionRe if (cookieHeader) { const tokens = cookieHeader.match(/sAccessToken=([^;]+)/g); if (tokens && tokens.length > 1) { - logger.warn(`Detected ${tokens.length} duplicate sAccessToken cookies, cleaning up`); + logger.warn(`Detected ${tokens.length} duplicate sAccessToken cookies - session is broken, forcing cleanup`); - const parsedTokens = tokens.map(tokenStr => { - const token = tokenStr.replace('sAccessToken=', ''); - try { - const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); - return { token, exp: payload.exp, iat: payload.iat }; - } catch (e) { - logger.error('Failed to parse token', e); - return { token, exp: 0, iat: 0 }; - } - }); - - parsedTokens.sort((a, b) => b.exp - a.exp); - const freshestToken = parsedTokens[0]; - - logger.info(`Using freshest token: exp=${freshestToken.exp}, iat=${freshestToken.iat}`); - - const cleanedCookie = cookieHeader - .split(';') - .filter(c => !c.trim().startsWith('sAccessToken=')) - .join(';') + `; sAccessToken=${freshestToken.token}`; - - const cleanedHeaders = new Headers(request.headers); - cleanedHeaders.set('cookie', cleanedCookie); - request = new Request(request, { headers: cleanedHeaders }); + return { + hasToken: false, + needsRefresh: true, + error: 'DUPLICATE_COOKIES_DETECTED', + duplicateCount: tokens.length + }; } } diff --git a/src/lib/tanstack-query/utils/ensure.ts b/src/lib/tanstack-query/utils/ensure.ts index 3b57a81..dbe3b80 100644 --- a/src/lib/tanstack-query/utils/ensure.ts +++ b/src/lib/tanstack-query/utils/ensure.ts @@ -23,9 +23,6 @@ export async function ensureServerQueryData( if (error?.options?.to && error?.options?.statusCode) { throw error; } - if (error instanceof Response && error.status === 440) { - throw error; - } throw error; } } diff --git a/src/lib/tanstack-query/utils/to-server-result.ts b/src/lib/tanstack-query/utils/to-server-result.ts index c812368..ed3c429 100644 --- a/src/lib/tanstack-query/utils/to-server-result.ts +++ b/src/lib/tanstack-query/utils/to-server-result.ts @@ -34,11 +34,6 @@ export const toServerResult = async ( } } - if (error instanceof Response && error.status === 440) { - logger.info('toServerResult: Re-throwing 440 Response for session refresh'); - throw error; - } - const duration = Date.now() - startTime; logger.error('Server Fn Error', error);