Make AuthContext track the private user doc

This commit is contained in:
Marshall Polaris 2022-08-09 23:54:46 -07:00
parent 0e4b317322
commit 50aff668dd
3 changed files with 50 additions and 20 deletions

View File

@ -1,10 +1,11 @@
import { ReactNode, createContext, useEffect } from 'react' import { ReactNode, createContext, useEffect } from 'react'
import { User } from 'common/user' import { User, PrivateUser } from 'common/user'
import { onIdTokenChanged } from 'firebase/auth' import { onIdTokenChanged } from 'firebase/auth'
import { import {
auth, auth,
listenForUser, listenForUser,
getUser, listenForPrivateUser,
getUserAndPrivateUser,
setCachedReferralInfoForUser, setCachedReferralInfoForUser,
} from 'web/lib/firebase/users' } from 'web/lib/firebase/users'
import { deleteTokenCookies, setTokenCookies } from 'web/lib/firebase/auth' import { deleteTokenCookies, setTokenCookies } from 'web/lib/firebase/auth'
@ -13,11 +14,13 @@ import { randomString } from 'common/util/random'
import { identifyUser, setUserProperty } from 'web/lib/service/analytics' import { identifyUser, setUserProperty } from 'web/lib/service/analytics'
import { useStateCheckEquality } from 'web/hooks/use-state-check-equality' import { useStateCheckEquality } from 'web/hooks/use-state-check-equality'
// Either we haven't looked up the logged in user yet (undefined), or we know type UserAndPrivateUser = { user: User; privateUser: PrivateUser }
// the user is not logged in (null), or we know the user is logged in (User).
type AuthUser = undefined | null | User
const CACHED_USER_KEY = 'CACHED_USER_KEY' // 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 = () => { const ensureDeviceToken = () => {
let deviceToken = localStorage.getItem('device-token') let deviceToken = localStorage.getItem('device-token')
@ -36,6 +39,7 @@ export function AuthProvider(props: {
}) { }) {
const { children, serverUser } = props const { children, serverUser } = props
const [authUser, setAuthUser] = useStateCheckEquality<AuthUser>(serverUser) const [authUser, setAuthUser] = useStateCheckEquality<AuthUser>(serverUser)
useEffect(() => { useEffect(() => {
if (serverUser === undefined) { if (serverUser === undefined) {
const cachedUser = localStorage.getItem(CACHED_USER_KEY) const cachedUser = localStorage.getItem(CACHED_USER_KEY)
@ -50,16 +54,16 @@ export function AuthProvider(props: {
id: await fbUser.getIdToken(), id: await fbUser.getIdToken(),
refresh: fbUser.refreshToken, refresh: fbUser.refreshToken,
}) })
let user = await getUser(fbUser.uid) let current = await getUserAndPrivateUser(fbUser.uid)
if (!user) { if (!current) {
const deviceToken = ensureDeviceToken() const deviceToken = ensureDeviceToken()
user = (await createUser({ deviceToken })).user as User current = (await createUser({ deviceToken })) as UserAndPrivateUser
} }
setAuthUser(user) setAuthUser(current)
// Persist to local storage, to reduce login blink next time. // Persist to local storage, to reduce login blink next time.
// Note: Cap on localStorage size is ~5mb // Note: Cap on localStorage size is ~5mb
localStorage.setItem(CACHED_USER_KEY, JSON.stringify(user)) localStorage.setItem(CACHED_USER_KEY, JSON.stringify(current))
setCachedReferralInfoForUser(user) setCachedReferralInfoForUser(current.user)
} else { } else {
// User logged out; reset to null // User logged out; reset to null
deleteTokenCookies() deleteTokenCookies()
@ -69,15 +73,30 @@ export function AuthProvider(props: {
}) })
}, [setAuthUser]) }, [setAuthUser])
const authUserId = authUser?.id const uid = authUser?.user.id
const authUsername = authUser?.username const username = authUser?.user.username
useEffect(() => { useEffect(() => {
if (authUserId && authUsername) { if (uid && username) {
identifyUser(authUserId) identifyUser(uid)
setUserProperty('username', authUsername) setUserProperty('username', username)
return listenForUser(authUserId, setAuthUser) 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()
}
} }
}, [authUserId, authUsername, setAuthUser]) }, [uid, username, setAuthUser])
return ( return (
<AuthContext.Provider value={authUser}>{children}</AuthContext.Provider> <AuthContext.Provider value={authUser}>{children}</AuthContext.Provider>

View File

@ -13,7 +13,8 @@ import {
import { AuthContext } from 'web/components/auth-context' import { AuthContext } from 'web/components/auth-context'
export const useUser = () => { export const useUser = () => {
return useContext(AuthContext) const authUser = useContext(AuthContext)
return authUser ? authUser.user : authUser
} }
export const usePrivateUser = (userId?: string) => { export const usePrivateUser = (userId?: string) => {

View File

@ -57,6 +57,16 @@ export async function getPrivateUser(userId: string) {
return (await getDoc(doc(privateUsers, userId))).data()! return (await getDoc(doc(privateUsers, userId))).data()!
} }
export async function getUserAndPrivateUser(userId: string) {
const [user, privateUser] = (
await Promise.all([
getDoc(doc(users, userId))!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
getDoc(doc(privateUsers, userId))!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
])
).map((d) => d.data()) as [User, PrivateUser]
return { user, privateUser }
}
export async function getUserByUsername(username: string) { export async function getUserByUsername(username: string) {
// Find a user whose username matches the given username, or null if no such user exists. // Find a user whose username matches the given username, or null if no such user exists.
const q = query(users, where('username', '==', username), limit(1)) const q = query(users, where('username', '==', username), limit(1))