@rebasepro/firebase 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,357 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { deepEqual as equal } from "fast-equals"
3
-
4
- import {
5
- ApplicationVerifier,
6
- Auth,
7
- ConfirmationResult,
8
- createUserWithEmailAndPassword as createUserWithEmailAndPasswordFirebase,
9
- FacebookAuthProvider,
10
- fetchSignInMethodsForEmail as fetchSignInMethodsForEmailFirebase,
11
- getAuth,
12
- GithubAuthProvider,
13
- GoogleAuthProvider,
14
- OAuthProvider,
15
- onAuthStateChanged,
16
- sendPasswordResetEmail as sendPasswordResetEmailFirebase,
17
- signInAnonymously,
18
- signInWithEmailAndPassword,
19
- signInWithPhoneNumber,
20
- signInWithPopup,
21
- signOut,
22
- TwitterAuthProvider,
23
- User as FirebaseUser
24
- } from "firebase/auth";
25
- import { FirebaseApp } from "firebase/app";
26
- import { FirebaseAuthController, FirebaseSignInOption, FirebaseSignInProvider, FirebaseUserWrapper } from "../types";
27
- import type { User } from "@rebasepro/types";
28
-
29
- /**
30
- * Resolve `user`'s roles through `defineRolesFor` and report whether they
31
- * differ from the ones already applied.
32
- *
33
- * A plain function rather than a check inside the hook because the check that
34
- * used to live there read `!equal(userRoles, userRoles)` — the local shadowed
35
- * the state of the same name, so the fresh roles were compared to themselves,
36
- * the guard was never true, and a `defineRolesFor` result arriving after the
37
- * auth-state change never reached the controller.
38
- */
39
- export async function resolveRoleRefresh(
40
- defineRolesFor: (user: User) => Promise<string[] | undefined> | string[] | undefined,
41
- user: User,
42
- currentRoles: string[] | undefined
43
- ): Promise<{ changed: boolean, roles: string[] | undefined }> {
44
- const roles = await defineRolesFor(user);
45
- return {
46
- changed: !equal(currentRoles, roles),
47
- roles
48
- };
49
- }
50
-
51
- export interface FirebaseAuthControllerProps {
52
- loading?: boolean;
53
- firebaseApp?: FirebaseApp;
54
- signInOptions?: Array<FirebaseSignInProvider | FirebaseSignInOption>;
55
- onSignOut?: () => void;
56
- defineRolesFor?: (user: User) => Promise<string[] | undefined> | string[] | undefined;
57
- }
58
-
59
- /**
60
- * Use this hook to build an {@link AuthController} based on Firebase Auth
61
- * @group Firebase
62
- */
63
- export const useFirebaseAuthController = <USER extends FirebaseUserWrapper = any, ExtraData = any>({
64
- loading,
65
- firebaseApp,
66
- signInOptions,
67
- onSignOut: onSignOutProp,
68
- defineRolesFor
69
- }: FirebaseAuthControllerProps): FirebaseAuthController<USER, ExtraData> => {
70
-
71
- const [loggedUser, setLoggedUser] = useState<FirebaseUser | null | undefined>(undefined); // logged user, anonymous or logged out
72
- const [authError, setAuthError] = useState<any>();
73
- const [authProviderError, setAuthProviderError] = useState<any>();
74
- const [initialLoading, setInitialLoading] = useState<boolean>(true);
75
- const [authLoading, setAuthLoading] = useState(true);
76
- const [loginSkipped, setLoginSkipped] = useState<boolean>(false);
77
- const [confirmationResult, setConfirmationResult] = useState<undefined | ConfirmationResult>();
78
- const [userRoles, _setUserRoles] = useState<string[] | undefined>();
79
- const [extra, setExtra] = useState<any>();
80
-
81
- const setUserRoles = useCallback((roles: string[] | undefined) => {
82
- if (!equal(userRoles, roles)) {
83
- _setUserRoles(roles);
84
- }
85
- }, [userRoles]);
86
-
87
- const authRef = useRef<Auth | null>(null);
88
-
89
- const updateUser = useCallback(async (user: FirebaseUser | null, initialize?: boolean) => {
90
- if (loading) return;
91
- if (defineRolesFor && user) {
92
- setUserRoles(await defineRolesFor(user));
93
- }
94
- setLoggedUser(user);
95
- setAuthLoading(false);
96
- if (initialize) {
97
- setInitialLoading(false);
98
- }
99
- }, [loading]);
100
-
101
- const updateRoles = useCallback(async (user: User | null) => {
102
- if (defineRolesFor && user) {
103
- const {
104
- changed,
105
- roles
106
- } = await resolveRoleRefresh(defineRolesFor, user, userRoles);
107
- if (changed) {
108
- setUserRoles(roles);
109
- }
110
- }
111
- }, [defineRolesFor, userRoles]);
112
-
113
- useEffect(() => {
114
- if (updateRoles && loggedUser) {
115
- updateRoles(loggedUser);
116
- }
117
- }, [updateRoles, loggedUser]);
118
-
119
- useEffect(() => {
120
- if (!firebaseApp) return;
121
- try {
122
- const auth = getAuth(firebaseApp);
123
- authRef.current = auth;
124
- setAuthError(undefined);
125
- updateUser(auth.currentUser, false)
126
- return onAuthStateChanged(
127
- auth,
128
- async (user) => {
129
- console.debug("User state changed", user);
130
- await updateUser(user, true);
131
- },
132
- error => setAuthProviderError(error)
133
- );
134
- } catch (e) {
135
- setAuthError(e);
136
- setInitialLoading(false);
137
- return () => {
138
- };
139
- }
140
- }, [firebaseApp, updateUser]);
141
-
142
- useEffect(() => {
143
- if (!loading && authRef.current) {
144
- updateUser(authRef.current.currentUser, false);
145
- }
146
- }, [loading, updateUser]);
147
-
148
- const getProviderOptions = useCallback((providerId: FirebaseSignInProvider): FirebaseSignInOption | undefined => {
149
- return signInOptions?.find((option) => {
150
- if (option === null) throw Error("useFirebaseAuthController");
151
- if (typeof option === "object" && option.provider === providerId)
152
- return option as FirebaseSignInOption;
153
- return undefined;
154
- }) as FirebaseSignInOption | undefined;
155
- }, []);
156
-
157
- const googleLogin = useCallback(() => {
158
- const provider = new GoogleAuthProvider();
159
- const options = getProviderOptions("google.com");
160
- if (options?.scopes)
161
- options.scopes.forEach((scope) => provider.addScope(scope));
162
- if (options?.customParameters) {
163
- provider.setCustomParameters(options.customParameters);
164
- } else {
165
- provider.setCustomParameters({
166
- prompt: "select_account"
167
- });
168
- }
169
- const auth = authRef.current;
170
- if (!auth) throw Error("No auth");
171
- signInWithPopup(auth, provider).catch(setAuthProviderError);
172
- }, [getProviderOptions]);
173
-
174
- const getAuthToken = useCallback(async (): Promise<string> => {
175
- if (!loggedUser)
176
- throw Error("No client user is logged in");
177
- if (!loggedUser.getIdToken) {
178
- throw Error("No getIdToken method available");
179
- }
180
- return loggedUser.getIdToken?.();
181
- }, [loggedUser]);
182
-
183
- const emailPasswordLogin = useCallback((email: string, password: string) => {
184
- const auth = authRef.current;
185
- if (!auth) throw Error("No auth");
186
- setAuthLoading(true);
187
- signInWithEmailAndPassword(auth, email, password)
188
- .catch(setAuthProviderError)
189
- .then(() => setAuthLoading(false));
190
- }, []);
191
-
192
- const createUserWithEmailAndPassword = useCallback((email: string, password: string) => {
193
- const auth = authRef.current;
194
- if (!auth) throw Error("No auth");
195
- setAuthLoading(true);
196
- createUserWithEmailAndPasswordFirebase(auth, email, password)
197
- .catch(setAuthProviderError)
198
- .then(() => setAuthLoading(false));
199
- }, []);
200
-
201
- const sendPasswordResetEmail = useCallback((email: string) => {
202
- const auth = authRef.current;
203
- if (!auth) throw Error("No auth");
204
- return sendPasswordResetEmailFirebase(auth, email)
205
- }, []);
206
-
207
- const fetchSignInMethodsForEmail = useCallback((email: string): Promise<string[]> => {
208
- const auth = authRef.current;
209
- if (!auth) throw Error("No auth");
210
- setAuthLoading(true);
211
- return fetchSignInMethodsForEmailFirebase(auth, email)
212
- .then((res) => {
213
- setAuthLoading(false);
214
- return res;
215
- });
216
- }, []);
217
-
218
- const onSignOut = useCallback(async () => {
219
- const auth = authRef.current;
220
- if (!auth) throw Error("No auth");
221
- await signOut(auth)
222
- .then(_ => {
223
- setLoggedUser(null);
224
- setUserRoles(undefined);
225
- setAuthProviderError(null);
226
- onSignOutProp?.();
227
- });
228
- setLoginSkipped(false);
229
- }, [onSignOutProp]);
230
-
231
- const doOauthLogin = useCallback((auth: Auth, provider: OAuthProvider | FacebookAuthProvider | GithubAuthProvider | TwitterAuthProvider) => {
232
- setAuthLoading(true);
233
- signInWithPopup(auth, provider)
234
- .catch(setAuthProviderError).then(() => setAuthLoading(false));
235
- }, []);
236
-
237
- const anonymousLogin = useCallback(() => {
238
- const auth = authRef.current;
239
- if (!auth) throw Error("No auth");
240
- setAuthLoading(true);
241
- signInAnonymously(auth)
242
- .catch(setAuthProviderError)
243
- .then(() => setAuthLoading(false));
244
- }, []);
245
-
246
- const phoneLogin = useCallback((phone: string, applicationVerifier: ApplicationVerifier) => {
247
- const auth = authRef.current;
248
- if (!auth) throw Error("No auth");
249
- setAuthLoading(true);
250
- return signInWithPhoneNumber(auth, phone, applicationVerifier)
251
- .catch(setAuthProviderError)
252
- .then((res) => {
253
- setAuthLoading(false);
254
- setConfirmationResult(res ?? undefined);
255
- });
256
- }, []);
257
-
258
- const appleLogin = useCallback(() => {
259
- const provider = new OAuthProvider("apple.com");
260
- const options = getProviderOptions("apple.com");
261
- if (options?.scopes)
262
- options.scopes.forEach((scope) => provider.addScope(scope));
263
- if (options?.customParameters)
264
- provider.setCustomParameters(options.customParameters);
265
- const auth = authRef.current;
266
- if (!auth) throw Error("No auth");
267
- doOauthLogin(auth, provider);
268
- }, [doOauthLogin, getProviderOptions]);
269
-
270
- const facebookLogin = useCallback(() => {
271
- const provider = new FacebookAuthProvider();
272
- const options = getProviderOptions("facebook.com");
273
- if (options?.scopes)
274
- options.scopes.forEach((scope) => provider.addScope(scope));
275
- if (options?.customParameters)
276
- provider.setCustomParameters(options.customParameters);
277
- const auth = authRef.current;
278
- if (!auth) throw Error("No auth");
279
- doOauthLogin(auth, provider);
280
- }, [doOauthLogin, getProviderOptions]);
281
-
282
- const githubLogin = useCallback(() => {
283
- const provider = new GithubAuthProvider();
284
- const options = getProviderOptions("github.com");
285
- if (options?.scopes)
286
- options.scopes.forEach((scope) => provider.addScope(scope));
287
- if (options?.customParameters)
288
- provider.setCustomParameters(options.customParameters);
289
- const auth = authRef.current;
290
- if (!auth) throw Error("No auth");
291
- doOauthLogin(auth, provider);
292
- }, [doOauthLogin, getProviderOptions]);
293
-
294
- const microsoftLogin = useCallback(() => {
295
- const provider = new OAuthProvider("microsoft.com");
296
- const options = getProviderOptions("microsoft.com");
297
- if (options?.scopes)
298
- options.scopes.forEach((scope) => provider.addScope(scope));
299
- if (options?.customParameters)
300
- provider.setCustomParameters(options.customParameters);
301
- const auth = authRef.current;
302
- if (!auth) throw Error("No auth");
303
- doOauthLogin(auth, provider);
304
- }, [doOauthLogin, getProviderOptions]);
305
-
306
- const twitterLogin = useCallback(() => {
307
- const provider = new TwitterAuthProvider();
308
- const options = getProviderOptions("twitter.com");
309
- if (options?.customParameters)
310
- provider.setCustomParameters(options.customParameters);
311
- const auth = authRef.current;
312
- if (!auth) throw Error("No auth");
313
- doOauthLogin(auth, provider);
314
- }, [doOauthLogin, getProviderOptions]);
315
-
316
- const skipLogin = useCallback(() => {
317
- setLoginSkipped(true);
318
- setLoggedUser(null);
319
- setUserRoles(undefined);
320
- }, []);
321
-
322
- const firebaseUserWrapper: FirebaseUserWrapper | null = loggedUser
323
- ? {
324
- ...loggedUser,
325
- roles: userRoles,
326
- firebaseUser: loggedUser
327
- }
328
- : null;
329
-
330
- return {
331
- user: firebaseUserWrapper as USER,
332
- setUser: updateUser,
333
- setUserRoles,
334
- authProviderError,
335
- authLoading,
336
- initialLoading: loading || initialLoading,
337
- signOut: onSignOut,
338
- getAuthToken,
339
- googleLogin,
340
- skipLogin,
341
- loginSkipped,
342
- emailPasswordLogin,
343
- createUserWithEmailAndPassword,
344
- sendPasswordResetEmail,
345
- fetchSignInMethodsForEmail,
346
- anonymousLogin,
347
- phoneLogin,
348
- appleLogin,
349
- facebookLogin,
350
- githubLogin,
351
- microsoftLogin,
352
- twitterLogin,
353
- confirmationResult,
354
- extra,
355
- setExtra
356
- };
357
- };