import { ReactNode, createContext, useEffect } from 'react' import { onIdTokenChanged } from 'firebase/auth' import { UserAndPrivateUser, auth, listenForUser, listenForPrivateUser, getUserAndPrivateUser, setCachedReferralInfoForUser, } from 'web/lib/firebase/users' import { deleteTokenCookies, setTokenCookies } from 'web/lib/firebase/auth' import { createUser } from 'web/lib/firebase/api' import { randomString } from 'common/util/random' import { identifyUser, setUserProperty } from 'web/lib/service/analytics' import { useStateCheckEquality } from 'web/hooks/use-state-check-equality' // Either we haven't looked up the logged in user yet (undefined), or we know // the user is not logged in (null), or we know the user is logged in. type AuthUser = undefined | null | UserAndPrivateUser const CACHED_USER_KEY = 'CACHED_USER_KEY_V2' const ensureDeviceToken = () => { let deviceToken = localStorage.getItem('device-token') if (!deviceToken) { deviceToken = randomString() localStorage.setItem('device-token', deviceToken) } return deviceToken } export const AuthContext = createContext(undefined) export function AuthProvider(props: { children: ReactNode serverUser?: AuthUser }) { const { children, serverUser } = props const [authUser, setAuthUser] = useStateCheckEquality(serverUser) useEffect(() => { if (serverUser === undefined) { const cachedUser = localStorage.getItem(CACHED_USER_KEY) setAuthUser(cachedUser && JSON.parse(cachedUser)) } }, [setAuthUser, serverUser]) useEffect(() => { return onIdTokenChanged(auth, async (fbUser) => { if (fbUser) { setTokenCookies({ id: await fbUser.getIdToken(), refresh: fbUser.refreshToken, }) let current = await getUserAndPrivateUser(fbUser.uid) if (!current.user || !current.privateUser) { const deviceToken = ensureDeviceToken() current = (await createUser({ deviceToken })) as UserAndPrivateUser } setAuthUser(current) // Persist to local storage, to reduce login blink next time. // Note: Cap on localStorage size is ~5mb localStorage.setItem(CACHED_USER_KEY, JSON.stringify(current)) setCachedReferralInfoForUser(current.user) } else { // User logged out; reset to null deleteTokenCookies() setAuthUser(null) localStorage.removeItem(CACHED_USER_KEY) } }) }, [setAuthUser]) const uid = authUser?.user.id const username = authUser?.user.username useEffect(() => { if (uid && username) { identifyUser(uid) setUserProperty('username', username) const userListener = listenForUser(uid, (user) => setAuthUser((authUser) => { /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ return { ...authUser!, user: user! } }) ) const privateUserListener = listenForPrivateUser(uid, (privateUser) => { setAuthUser((authUser) => { /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ return { ...authUser!, privateUser: privateUser! } }) }) return () => { userListener() privateUserListener() } } }, [uid, username, setAuthUser]) return ( {children} ) }