1- import { type QueryClient , useQuery } from '@tanstack/react-query'
1+ import { type QueryClient , useQuery , useQueryClient } from '@tanstack/react-query'
22import { client } from '@/lib/auth/auth-client'
33import {
44 type AppSession ,
@@ -12,8 +12,14 @@ export const sessionKeys = {
1212 detail : ( ) => [ ...sessionKeys . all , 'detail' ] as const ,
1313}
1414
15- async function fetchSession ( signal ?: AbortSignal ) : Promise < AppSession > {
16- const res = await client . getSession ( { fetchOptions : { signal } } )
15+ async function fetchSession (
16+ signal ?: AbortSignal ,
17+ disableCookieCache ?: boolean
18+ ) : Promise < AppSession > {
19+ const res = await client . getSession ( {
20+ ...( disableCookieCache ? { query : { disableCookieCache : true } } : { } ) ,
21+ fetchOptions : { signal } ,
22+ } )
1723 return extractSessionDataFromAuthClientResult ( res ) as AppSession
1824}
1925
@@ -35,6 +41,8 @@ export async function refreshSessionQuery(queryClient: QueryClient): Promise<App
3541 return fresh
3642}
3743
44+ export const IMPERSONATION_REFETCH_INTERVAL = 60 * 1000
45+
3846/**
3947 * Reads the current Better Auth session via the client SDK.
4048 *
@@ -44,12 +52,29 @@ export async function refreshSessionQuery(queryClient: QueryClient): Promise<App
4452 * `retry: false` preserves the prior fail-fast contract: an auth failure (expired
4553 * token, startup network partition) surfaces immediately rather than retrying a
4654 * request that won't succeed.
55+ *
56+ * While the session is an impersonation session, the query polls and refetches
57+ * on focus (overriding the global `refetchOnWindowFocus: false`) so an expiry —
58+ * including one slept through with the laptop closed — settles the query to
59+ * `null` and surfaces the impersonation-expired recovery screen. Those
60+ * refetches also bypass Better Auth's cookie cache: it can otherwise keep
61+ * vouching for a session that was expired or revoked server-side, and the
62+ * expiry detection shouldn't depend on the cache's own TTL details.
63+ * Impersonation sessions are short-lived and admin-only, so none of these
64+ * overrides affect normal sessions.
4765 */
4866export function useSessionQuery ( ) {
67+ const queryClient = useQueryClient ( )
4968 return useQuery ( {
5069 queryKey : sessionKeys . detail ( ) ,
51- queryFn : ( { signal } ) => fetchSession ( signal ) ,
70+ queryFn : ( { signal } ) => {
71+ const cached = queryClient . getQueryData < AppSession > ( sessionKeys . detail ( ) )
72+ return fetchSession ( signal , Boolean ( cached ?. session ?. impersonatedBy ) )
73+ } ,
5274 staleTime : SESSION_STALE_TIME ,
5375 retry : false ,
76+ refetchInterval : ( query ) =>
77+ query . state . data ?. session ?. impersonatedBy ? IMPERSONATION_REFETCH_INTERVAL : false ,
78+ refetchOnWindowFocus : ( query ) => Boolean ( query . state . data ?. session ?. impersonatedBy ) ,
5479 } )
5580}
0 commit comments