Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions src/runtime/app/composables/useUserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,33 @@ export function useUserSession(): UseUserSessionReturn {
}
}

function queueHydrationReconciliation() {
if (hydrationReconcileQueued.value)
return

hydrationReconcileQueued.value = true
nuxtApp.hook('app:mounted', async () => {
await fetchSession({ force: true })
hydrationReconcileQueued.value = false
})
}

// On client, subscribe to better-auth's reactive session store
if (runtimeFlags.client && rawClient) {
const clientSession = rawClient.useSession()
const initialClientSession = clientSession.value

const shouldReconcileInitialHydration
= nuxtApp.isHydrating
&& nuxtApp.payload.serverRendered
&& Boolean(session.value && user.value)
&& !initialClientSession?.data?.session
&& !initialClientSession?.data?.user
&& !initialClientSession?.isPending
&& !initialClientSession?.isRefetching

if (shouldReconcileInitialHydration)
queueHydrationReconciliation()

watch(
() => clientSession.value,
Expand All @@ -148,13 +172,7 @@ export function useUserSession(): UseUserSessionReturn {
&& !newSession?.data?.user

if (isHydrationEmptySnapshot) {
if (!hydrationReconcileQueued.value) {
hydrationReconcileQueued.value = true
nuxtApp.hook('app:mounted', async () => {
await fetchSession({ force: true })
hydrationReconcileQueued.value = false
})
}
queueHydrationReconciliation()
return
}

Expand All @@ -163,7 +181,6 @@ export function useUserSession(): UseUserSessionReturn {
if (!authReady.value && !newSession?.isPending && !newSession?.isRefetching)
authReady.value = true
},
{ immediate: true },
)
}

Expand Down
23 changes: 17 additions & 6 deletions test/use-user-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,16 @@ describe('useUserSession hydration bootstrap', () => {
delete (globalThis as { __NUXT_BETTER_AUTH_TEST_FLAGS__?: { client: boolean, server: boolean } }).__NUXT_BETTER_AUTH_TEST_FLAGS__
})

it('bootstraps client session by default even when SSR payload is hydrated', async () => {
it('subscribes without synchronously bridging the initial client snapshot', async () => {
payload.serverRendered = true
seedHydratedState()

const useUserSession = await loadUseUserSession()
const auth = useUserSession()

expect(auth.ready.value).toBe(true)
expect(auth.ready.value).toBe(false)
expect(auth.session.value).toEqual({ id: 'session-1' })
expect(auth.user.value).toEqual({ id: 'user-1' })
expect(mockClient.useSession).toHaveBeenCalledOnce()
})

Expand Down Expand Up @@ -283,6 +285,7 @@ describe('useUserSession hydration bootstrap', () => {
payload.serverRendered = true
nuxtApp.isHydrating = true
seedHydratedState()
sessionAtom.value.isPending = true

mockClient.getSession.mockResolvedValueOnce({
data: {
Expand All @@ -296,9 +299,15 @@ describe('useUserSession hydration bootstrap', () => {
await flushPromises()

expect(mockClient.getSession).not.toHaveBeenCalled()
expect(nuxtHooks.get('app:mounted')).toBeUndefined()
expect(auth.session.value).toEqual({ id: 'session-1' })
expect(auth.user.value).toEqual({ id: 'user-1' })

sessionAtom.value = { ...sessionAtom.value, isPending: false }
await flushPromises()

expect((nuxtHooks.get('app:mounted') || [])).toHaveLength(1)

nuxtApp.isHydrating = false
await triggerNuxtHook('app:mounted')
await flushPromises()
Expand Down Expand Up @@ -850,16 +859,20 @@ describe('useUserSession hydration bootstrap', () => {
const useUserSession = await loadUseUserSession()
const auth = useUserSession()

expect(auth.ready.value).toBe(true)
expect(auth.ready.value).toBe(false)

sessionAtom.value = refreshedSession
await flushPromises()

expect(auth.ready.value).toBe(true)
expect(auth.session.value).toEqual({ id: 'session-3', ipAddress: '127.0.0.1' })
expect(auth.user.value).toEqual({ id: 'user-3', email: 'user3@example.com' })
})

it('does not re-sync session for nested mutations within the current Better Auth snapshot', async () => {
const useUserSession = await loadUseUserSession()
const auth = useUserSession()

sessionAtom.value = {
data: {
session: { id: 'session-1', metadata: { role: 'member' } },
Expand All @@ -869,9 +882,7 @@ describe('useUserSession hydration bootstrap', () => {
isRefetching: false,
error: null,
}

const useUserSession = await loadUseUserSession()
const auth = useUserSession()
await flushPromises()
const bridgedSession = auth.session.value

const metadata = sessionAtom.value.data!.session.metadata as { role: string }
Expand Down
Loading