-
-
Notifications
You must be signed in to change notification settings - Fork 344
/
Copy pathuser.ts
184 lines (169 loc) · 5.36 KB
/
user.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import type { FirebaseApp } from 'firebase/app'
import {
onIdTokenChanged,
type User,
updateEmail,
updateProfile,
reauthenticateWithCredential,
type AuthCredential,
type Auth,
} from 'firebase/auth'
import { computed, Ref } from 'vue-demi'
import { useFirebaseApp } from '../app'
import type { _Nullable } from '../shared'
/**
* Maps an application to a user
* @internal
*/
export const authUserMap = new WeakMap<FirebaseApp, Ref<_Nullable<User>>>()
/**
* Returns a reactive variable of the currently authenticated user in the firebase app. The ref is null if no user is
* authenticated or when the user logs out. The ref is undefined until the user is initially loaded.
* @param name - name of the application
*/
export function useCurrentUser(name?: string) {
// TODO: write a test
if (
process.env.NODE_ENV !== 'production' &&
!authUserMap.has(useFirebaseApp(name))
) {
throw new Error(
`[VueFire] useCurrentUser() called before the VueFireAuth module was added to the VueFire plugin. This will fail in production.`
)
}
return authUserMap.get(useFirebaseApp(name))!
}
/**
* Helper that returns a computed boolean that becomes `true` as soon as the current user is no longer `undefined`. Note
* this doesn't ensure the user is logged in, only if the initial signing process has run.
*
* @param name - name of the application
*/
export function useIsCurrentUserLoaded(name?: string) {
const currentUser = useCurrentUser(name)
return computed(() => currentUser.value !== undefined)
}
/**
* Updates the current user profile and updates the current user state. This function internally calls `updateProfile()`
* from 'firebase/auth' and then updates the current user state.
*
* @param profile - the new profile information
*/
export function updateCurrentUserProfile(profile: {
displayName?: _Nullable<string>
photoURL?: _Nullable<string>
}): Promise<void> {
return getCurrentUser().then((user) => {
if (user) {
return updateProfile(user, profile).then(() => user.reload())
}
})
}
/**
* Updates the current user and synchronizes the current user state. This function internally calls `updateEmail()`
*
* @experimental
*
* @param newEmail - the new email address
* @param credential -
*/
export function updateCurrentUserEmail(
newEmail: string,
credential: AuthCredential
) {
return getCurrentUser()
.then((user) => {
if (user) {
// TODO: Maybe this whole function should be dropped since it depends on re-authenticating first or we should
// let the user do it. Otherwise, we need a way to retrieve the credential token when logging in
reauthenticateWithCredential(user, credential)
}
return user
})
.then((user) => {
if (user) {
return updateEmail(user, newEmail).then(() => {
// @ts-expect-error: readonly property
user.email = newEmail
})
}
})
}
// @internal
type _UserState =
// state 1 waiting for the initial load: [Promise, resolveFn]
| [Promise<_Nullable<User>>, (user: Ref<_Nullable<User>>) => void]
// state 2 loaded
| Ref<_Nullable<User>>
/**
* Map of user promises based on the firebase application. Used by `getCurrentUser()` to return a promise that resolves
* the current user.
* @internal
*/
export const initialUserMap = new WeakMap<FirebaseApp, _UserState>()
/**
* Forcibly sets the initial user state. This is used by the server auth module to set the initial user state and make
* `getCurrentUser()` work on the server during navigation and such.
*
* @internal
*
* @param firebaseApp - the firebase application
* @param user - the user to set
*/
export function _setInitialUser(
firebaseApp: FirebaseApp,
user: Ref<_Nullable<User>>
) {
initialUserMap.set(firebaseApp, user)
}
/**
* @internal
* @param name - name of the application
*/
function _getCurrentUserState(name?: string) {
const firebaseApp = useFirebaseApp(name)
if (!initialUserMap.has(firebaseApp)) {
let resolve!: (resolvedUser: _Nullable<User>) => void
const promise = new Promise<_Nullable<User>>((_resolve) => {
resolve = _resolve
})
const userState: _UserState = [
promise,
(user: Ref<_Nullable<User>>) => {
initialUserMap.set(firebaseApp, user)
// resolve the actual promise
resolve(user.value)
},
]
initialUserMap.set(firebaseApp, userState)
}
return initialUserMap.get(firebaseApp)!
}
/**
* Returns a promise that resolves the current user once the user is loaded. Must be called after the firebase app is
* initialized.
* @param name - name of the firebase application
*/
export function getCurrentUser(name?: string): Promise<_Nullable<User>> {
const userOrPromise = _getCurrentUserState(name)
return Array.isArray(userOrPromise)
? userOrPromise[0]
: Promise.resolve(userOrPromise.value)
}
export function setupOnAuthStateChanged(
user: Ref<_Nullable<User>>,
auth: Auth
) {
// onAuthStateChanged doesn't trigger in all scenarios like when the user goes links an existing account and their
// data is updated
// https://github.com/firebase/firebase-js-sdk/issues/4227
onIdTokenChanged(auth, (userData) => {
const userOrPromise = _getCurrentUserState()
user.value = userData
// setup the initial user
// afterwards, this will never be an array
if (Array.isArray(userOrPromise)) {
userOrPromise[1](user)
}
})
}