Files
supabase/packages/common/auth.tsx
Joshen Lim 944c5862f3 Chore/small refactors (#47740)
## Context

Just extracting the fixes which I think are applicable from this
[PR](https://github.com/supabase/supabase/pull/47695)

Main files are
- `apps/studio/hooks/analytics/useLogsQuery.tsx`
- `packages/common/auth.tsx`
- `packages/common/feature-flags.tsx`

## Changes involved
- Adjust `useLogsQuery` to accept an object as prop, rather than 4
individual params
- This one doesn't address any Sentry issues, but is just a improvement
to the function's API imo, more readable
- Adjust how user email is retrieved in `feature-flags`
- Related Sentry issue
[here](https://supabase.sentry.io/issues/7592718607/?project=5459134)
- The error is a bit vague, but Claude's attempt to fix looks alright in
general IMO
  - Minimally verified that feature flags are loading as expected still

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved log-related screens and queries for more reliable loading and
filtering across the app.
* Fixed profile and account data handling so identity details are
retrieved more consistently.
* Improved authentication handling to better recognize missing user data
and keep the app stable.
* Updated feature flag personalization to use more accurate account
information.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-08 23:21:48 +08:00

186 lines
4.1 KiB
TypeScript

'use client'
import type { AuthError, Session } from '@supabase/supabase-js'
import {
createContext,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import { clearLocalStorage } from './constants/local-storage'
import { gotrueClient, type User } from './gotrue'
export type { User }
const DEFAULT_SESSION: any = {
access_token: undefined,
expires_at: 0,
expires_in: 0,
refresh_token: '',
token_type: '',
user: {
aud: '',
app_metadata: {},
confirmed_at: '',
created_at: '',
email: '',
email_confirmed_at: '',
id: '',
identities: [],
last_signed_in_at: '',
phone: '',
role: '',
updated_at: '',
user_metadata: {},
},
} as unknown as Session
/* Auth Context */
type AuthState =
| {
session: Session | null
error: AuthError | null
isLoading: false
}
| {
session: null
error: AuthError | null
isLoading: true
}
export type AuthContext = { refreshSession: () => Promise<Session | null> } & AuthState
export const AuthContext = createContext<AuthContext>({
session: null,
error: null,
isLoading: true,
refreshSession: () => Promise.resolve(null),
})
export type AuthProviderProps = {
alwaysLoggedIn?: boolean
}
export const AuthProvider = ({
alwaysLoggedIn,
children,
}: PropsWithChildren<AuthProviderProps>) => {
const [state, setState] = useState<AuthState>({ session: null, error: null, isLoading: true })
useEffect(() => {
let mounted = true
gotrueClient.initialize().then(({ error }) => {
if (mounted && error !== null) {
setState((prev) => ({ ...prev, error }))
}
})
return () => {
mounted = false
}
}, [])
// Keep the session in sync
useEffect(() => {
const {
data: { subscription },
} = gotrueClient.onAuthStateChange((_event, session) => {
setState((prev) => ({
session,
// If there is a session, we clear the error
error: session !== null ? null : prev.error,
isLoading: false,
}))
})
return subscription.unsubscribe
}, [])
// Helper method to refresh the session.
// For example after a user updates their profile
const refreshSession = useCallback(async () => {
const {
data: { session },
} = await gotrueClient.refreshSession()
return session
}, [])
const value = useMemo(() => {
if (alwaysLoggedIn) {
return { session: DEFAULT_SESSION, error: null, isLoading: false, refreshSession } as const
} else {
return { ...state, refreshSession } as const
}
}, [state, refreshSession])
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
/* Auth Utils */
export const useAuth = () => useContext(AuthContext)
export const useSession = () => useAuth().session
export const useUser = () => {
const user = useSession()?.user ?? null
// auth-js substitutes a throwing proxy for `user` when it was configured with
// `userStorage` and hasn't migrated the user into storage yet. Treat that as
// "no user" instead of crashing whenever a property on it is read.
if (user && (user as any).__isUserNotAvailableProxy) return null
return user
}
export const useIsUserLoading = () => useAuth().isLoading
export const useIsLoggedIn = () => {
const user = useUser()
return user !== null
}
export const useAuthError = () => useAuth().error
export const useIsMFAEnabled = () => {
const user = useUser()
return user !== null && user.factors && user.factors.length > 0
}
export const signOut = async () => await gotrueClient.signOut()
export const logOut = async () => {
await signOut()
clearLocalStorage()
}
gotrueClient.onAuthStateChange((_event, _session) => {})
/**
* Gets a current access token.
*
* Calls getSession, which will refresh the token if needed.
*/
export async function getAccessToken() {
// ignore if server-side
if (typeof window === 'undefined') return undefined
const {
data: { session },
error,
} = await gotrueClient.getSession()
if (error) {
throw error
}
return session?.access_token
}