auth.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. 'use client'
  2. import type { AuthError, Session } from '@supabase/supabase-js'
  3. import {
  4. createContext,
  5. PropsWithChildren,
  6. useCallback,
  7. useContext,
  8. useEffect,
  9. useMemo,
  10. useState,
  11. } from 'react'
  12. import { clearLocalStorage } from './constants/local-storage'
  13. import { gotrueClient, type User } from './gotrue'
  14. export type { User }
  15. const DEFAULT_SESSION: any = {
  16. access_token: undefined,
  17. expires_at: 0,
  18. expires_in: 0,
  19. refresh_token: '',
  20. token_type: '',
  21. user: {
  22. aud: '',
  23. app_metadata: {},
  24. confirmed_at: '',
  25. created_at: '',
  26. email: '',
  27. email_confirmed_at: '',
  28. id: '',
  29. identities: [],
  30. last_signed_in_at: '',
  31. phone: '',
  32. role: '',
  33. updated_at: '',
  34. user_metadata: {},
  35. },
  36. } as unknown as Session
  37. /* Auth Context */
  38. type AuthState =
  39. | {
  40. session: Session | null
  41. error: AuthError | null
  42. isLoading: false
  43. }
  44. | {
  45. session: null
  46. error: AuthError | null
  47. isLoading: true
  48. }
  49. export type AuthContext = { refreshSession: () => Promise<Session | null> } & AuthState
  50. export const AuthContext = createContext<AuthContext>({
  51. session: null,
  52. error: null,
  53. isLoading: true,
  54. refreshSession: () => Promise.resolve(null),
  55. })
  56. export type AuthProviderProps = {
  57. alwaysLoggedIn?: boolean
  58. }
  59. export const AuthProvider = ({
  60. alwaysLoggedIn,
  61. children,
  62. }: PropsWithChildren<AuthProviderProps>) => {
  63. const [state, setState] = useState<AuthState>({ session: null, error: null, isLoading: true })
  64. useEffect(() => {
  65. let mounted = true
  66. gotrueClient.initialize().then(({ error }) => {
  67. if (mounted && error !== null) {
  68. setState((prev) => ({ ...prev, error }))
  69. }
  70. })
  71. return () => {
  72. mounted = false
  73. }
  74. }, [])
  75. // Keep the session in sync
  76. useEffect(() => {
  77. const {
  78. data: { subscription },
  79. } = gotrueClient.onAuthStateChange((_event, session) => {
  80. setState((prev) => ({
  81. session,
  82. // If there is a session, we clear the error
  83. error: session !== null ? null : prev.error,
  84. isLoading: false,
  85. }))
  86. })
  87. return subscription.unsubscribe
  88. }, [])
  89. // Helper method to refresh the session.
  90. // For example after a user updates their profile
  91. const refreshSession = useCallback(async () => {
  92. const {
  93. data: { session },
  94. } = await gotrueClient.refreshSession()
  95. return session
  96. }, [])
  97. const value = useMemo(() => {
  98. if (alwaysLoggedIn) {
  99. return { session: DEFAULT_SESSION, error: null, isLoading: false, refreshSession } as const
  100. } else {
  101. return { ...state, refreshSession } as const
  102. }
  103. }, [state, refreshSession])
  104. return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
  105. }
  106. /* Auth Utils */
  107. export const useAuth = () => useContext(AuthContext)
  108. export const useSession = () => useAuth().session
  109. export const useUser = () => useSession()?.user ?? null
  110. export const useIsUserLoading = () => useAuth().isLoading
  111. export const useIsLoggedIn = () => {
  112. const user = useUser()
  113. return user !== null
  114. }
  115. export const useAuthError = () => useAuth().error
  116. export const useIsMFAEnabled = () => {
  117. const user = useUser()
  118. return user !== null && user.factors && user.factors.length > 0
  119. }
  120. export const signOut = async () => await gotrueClient.signOut()
  121. export const logOut = async () => {
  122. await signOut()
  123. clearLocalStorage()
  124. }
  125. gotrueClient.onAuthStateChange((_event, _session) => {})
  126. /**
  127. * Gets a current access token.
  128. *
  129. * Calls getSession, which will refresh the token if needed.
  130. */
  131. export async function getAccessToken() {
  132. // ignore if server-side
  133. if (typeof window === 'undefined') return undefined
  134. const {
  135. data: { session },
  136. error,
  137. } = await gotrueClient.getSession()
  138. if (error) {
  139. throw error
  140. }
  141. return session?.access_token
  142. }