@rebasepro/firebase 0.9.1-canary.fd3754b → 0.10.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.
package/dist/index.umd.js DELETED
@@ -1,2705 +0,0 @@
1
- (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react"), require("fast-equals"), require("@firebase/auth"), require("@firebase/storage"), require("@firebase/app"), require("@firebase/app-check"), require("@rebasepro/types"), require("@firebase/firestore"), require("@rebasepro/common"), require("fuse.js"), require("@firebase/functions"), require("@firebase/database"), require("@rebasepro/utils"), require("@rebasepro/app"), require("@rebasepro/admin"), require("@rebasepro/ui"), require("react-router-dom"), require("react/jsx-runtime")) : typeof define === "function" && define.amd ? define([
3
- "exports",
4
- "react",
5
- "fast-equals",
6
- "@firebase/auth",
7
- "@firebase/storage",
8
- "@firebase/app",
9
- "@firebase/app-check",
10
- "@rebasepro/types",
11
- "@firebase/firestore",
12
- "@rebasepro/common",
13
- "fuse.js",
14
- "@firebase/functions",
15
- "@firebase/database",
16
- "@rebasepro/utils",
17
- "@rebasepro/app",
18
- "@rebasepro/admin",
19
- "@rebasepro/ui",
20
- "react-router-dom",
21
- "react/jsx-runtime"
22
- ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Firebase"] = {}, global.react, global.fastEquals, global._firebase_auth, global._firebase_storage, global._firebase_app, global._firebase_app_check, global._rebasepro_types, global._firebase_firestore, global._rebasepro_common, global.fuse_js, global._firebase_functions, global._firebase_database, global._rebasepro_utils, global._rebasepro_app, global._rebasepro_admin, global._rebasepro_ui, global.react_router_dom, global.react_jsx_runtime));
23
- })(this, function(exports, react, fast_equals, _firebase_auth, _firebase_storage, _firebase_app, _firebase_app_check, _rebasepro_types, _firebase_firestore, _rebasepro_common, fuse_js, _firebase_functions, _firebase_database, _rebasepro_utils, _rebasepro_app, _rebasepro_admin, _rebasepro_ui, react_router_dom, react_jsx_runtime) {
24
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
- //#region \0rolldown/runtime.js
26
- var __create = Object.create;
27
- var __defProp = Object.defineProperty;
28
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
29
- var __getOwnPropNames = Object.getOwnPropertyNames;
30
- var __getProtoOf = Object.getPrototypeOf;
31
- var __hasOwnProp = Object.prototype.hasOwnProperty;
32
- var __copyProps = (to, from, except, desc) => {
33
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
34
- key = keys[i];
35
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
36
- get: ((k) => from[k]).bind(null, key),
37
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
38
- });
39
- }
40
- return to;
41
- };
42
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
43
- value: mod,
44
- enumerable: true
45
- }) : target, mod));
46
- //#endregion
47
- react = __toESM(react, 1);
48
- fuse_js = __toESM(fuse_js, 1);
49
- //#region src/hooks/useFirebaseAuthController.ts
50
- /**
51
- * Use this hook to build an {@link AuthController} based on Firebase Auth
52
- * @group Firebase
53
- */
54
- var useFirebaseAuthController = ({ loading, firebaseApp, signInOptions, onSignOut: onSignOutProp, defineRolesFor }) => {
55
- const [loggedUser, setLoggedUser] = (0, react.useState)(void 0);
56
- const [authError, setAuthError] = (0, react.useState)();
57
- const [authProviderError, setAuthProviderError] = (0, react.useState)();
58
- const [initialLoading, setInitialLoading] = (0, react.useState)(true);
59
- const [authLoading, setAuthLoading] = (0, react.useState)(true);
60
- const [loginSkipped, setLoginSkipped] = (0, react.useState)(false);
61
- const [confirmationResult, setConfirmationResult] = (0, react.useState)();
62
- const [userRoles, _setUserRoles] = (0, react.useState)();
63
- const [extra, setExtra] = (0, react.useState)();
64
- const setUserRoles = (0, react.useCallback)((roles) => {
65
- if (!(0, fast_equals.deepEqual)(userRoles, roles)) _setUserRoles(roles);
66
- }, [userRoles]);
67
- const authRef = (0, react.useRef)(null);
68
- const updateUser = (0, react.useCallback)(async (user, initialize) => {
69
- if (loading) return;
70
- if (defineRolesFor && user) setUserRoles(await defineRolesFor(user));
71
- setLoggedUser(user);
72
- setAuthLoading(false);
73
- if (initialize) setInitialLoading(false);
74
- }, [loading]);
75
- const updateRoles = (0, react.useCallback)(async (user) => {
76
- if (defineRolesFor && user) {
77
- const userRoles = await defineRolesFor(user);
78
- if (!(0, fast_equals.deepEqual)(userRoles, userRoles)) setUserRoles(userRoles);
79
- }
80
- }, [defineRolesFor, userRoles]);
81
- (0, react.useEffect)(() => {
82
- if (updateRoles && loggedUser) updateRoles(loggedUser);
83
- }, [updateRoles, loggedUser]);
84
- (0, react.useEffect)(() => {
85
- if (!firebaseApp) return;
86
- try {
87
- const auth = (0, _firebase_auth.getAuth)(firebaseApp);
88
- authRef.current = auth;
89
- setAuthError(void 0);
90
- updateUser(auth.currentUser, false);
91
- return (0, _firebase_auth.onAuthStateChanged)(auth, async (user) => {
92
- console.debug("User state changed", user);
93
- await updateUser(user, true);
94
- }, (error) => setAuthProviderError(error));
95
- } catch (e) {
96
- setAuthError(e);
97
- setInitialLoading(false);
98
- return () => {};
99
- }
100
- }, [firebaseApp, updateUser]);
101
- (0, react.useEffect)(() => {
102
- if (!loading && authRef.current) updateUser(authRef.current.currentUser, false);
103
- }, [loading, updateUser]);
104
- const getProviderOptions = (0, react.useCallback)((providerId) => {
105
- return signInOptions?.find((option) => {
106
- if (option === null) throw Error("useFirebaseAuthController");
107
- if (typeof option === "object" && option.provider === providerId) return option;
108
- });
109
- }, []);
110
- const googleLogin = (0, react.useCallback)(() => {
111
- const provider = new _firebase_auth.GoogleAuthProvider();
112
- const options = getProviderOptions("google.com");
113
- if (options?.scopes) options.scopes.forEach((scope) => provider.addScope(scope));
114
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
115
- else provider.setCustomParameters({ prompt: "select_account" });
116
- const auth = authRef.current;
117
- if (!auth) throw Error("No auth");
118
- (0, _firebase_auth.signInWithPopup)(auth, provider).catch(setAuthProviderError);
119
- }, [getProviderOptions]);
120
- const getAuthToken = (0, react.useCallback)(async () => {
121
- if (!loggedUser) throw Error("No client user is logged in");
122
- if (!loggedUser.getIdToken) throw Error("No getIdToken method available");
123
- return loggedUser.getIdToken?.();
124
- }, [loggedUser]);
125
- const emailPasswordLogin = (0, react.useCallback)((email, password) => {
126
- const auth = authRef.current;
127
- if (!auth) throw Error("No auth");
128
- setAuthLoading(true);
129
- (0, _firebase_auth.signInWithEmailAndPassword)(auth, email, password).catch(setAuthProviderError).then(() => setAuthLoading(false));
130
- }, []);
131
- const createUserWithEmailAndPassword = (0, react.useCallback)((email, password) => {
132
- const auth = authRef.current;
133
- if (!auth) throw Error("No auth");
134
- setAuthLoading(true);
135
- (0, _firebase_auth.createUserWithEmailAndPassword)(auth, email, password).catch(setAuthProviderError).then(() => setAuthLoading(false));
136
- }, []);
137
- const sendPasswordResetEmail = (0, react.useCallback)((email) => {
138
- const auth = authRef.current;
139
- if (!auth) throw Error("No auth");
140
- return (0, _firebase_auth.sendPasswordResetEmail)(auth, email);
141
- }, []);
142
- const fetchSignInMethodsForEmail = (0, react.useCallback)((email) => {
143
- const auth = authRef.current;
144
- if (!auth) throw Error("No auth");
145
- setAuthLoading(true);
146
- return (0, _firebase_auth.fetchSignInMethodsForEmail)(auth, email).then((res) => {
147
- setAuthLoading(false);
148
- return res;
149
- });
150
- }, []);
151
- const onSignOut = (0, react.useCallback)(async () => {
152
- const auth = authRef.current;
153
- if (!auth) throw Error("No auth");
154
- await (0, _firebase_auth.signOut)(auth).then((_) => {
155
- setLoggedUser(null);
156
- setUserRoles(void 0);
157
- setAuthProviderError(null);
158
- onSignOutProp?.();
159
- });
160
- setLoginSkipped(false);
161
- }, [onSignOutProp]);
162
- const doOauthLogin = (0, react.useCallback)((auth, provider) => {
163
- setAuthLoading(true);
164
- (0, _firebase_auth.signInWithPopup)(auth, provider).catch(setAuthProviderError).then(() => setAuthLoading(false));
165
- }, []);
166
- const anonymousLogin = (0, react.useCallback)(() => {
167
- const auth = authRef.current;
168
- if (!auth) throw Error("No auth");
169
- setAuthLoading(true);
170
- (0, _firebase_auth.signInAnonymously)(auth).catch(setAuthProviderError).then(() => setAuthLoading(false));
171
- }, []);
172
- const phoneLogin = (0, react.useCallback)((phone, applicationVerifier) => {
173
- const auth = authRef.current;
174
- if (!auth) throw Error("No auth");
175
- setAuthLoading(true);
176
- return (0, _firebase_auth.signInWithPhoneNumber)(auth, phone, applicationVerifier).catch(setAuthProviderError).then((res) => {
177
- setAuthLoading(false);
178
- setConfirmationResult(res ?? void 0);
179
- });
180
- }, []);
181
- const appleLogin = (0, react.useCallback)(() => {
182
- const provider = new _firebase_auth.OAuthProvider("apple.com");
183
- const options = getProviderOptions("apple.com");
184
- if (options?.scopes) options.scopes.forEach((scope) => provider.addScope(scope));
185
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
186
- const auth = authRef.current;
187
- if (!auth) throw Error("No auth");
188
- doOauthLogin(auth, provider);
189
- }, [doOauthLogin, getProviderOptions]);
190
- const facebookLogin = (0, react.useCallback)(() => {
191
- const provider = new _firebase_auth.FacebookAuthProvider();
192
- const options = getProviderOptions("facebook.com");
193
- if (options?.scopes) options.scopes.forEach((scope) => provider.addScope(scope));
194
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
195
- const auth = authRef.current;
196
- if (!auth) throw Error("No auth");
197
- doOauthLogin(auth, provider);
198
- }, [doOauthLogin, getProviderOptions]);
199
- const githubLogin = (0, react.useCallback)(() => {
200
- const provider = new _firebase_auth.GithubAuthProvider();
201
- const options = getProviderOptions("github.com");
202
- if (options?.scopes) options.scopes.forEach((scope) => provider.addScope(scope));
203
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
204
- const auth = authRef.current;
205
- if (!auth) throw Error("No auth");
206
- doOauthLogin(auth, provider);
207
- }, [doOauthLogin, getProviderOptions]);
208
- const microsoftLogin = (0, react.useCallback)(() => {
209
- const provider = new _firebase_auth.OAuthProvider("microsoft.com");
210
- const options = getProviderOptions("microsoft.com");
211
- if (options?.scopes) options.scopes.forEach((scope) => provider.addScope(scope));
212
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
213
- const auth = authRef.current;
214
- if (!auth) throw Error("No auth");
215
- doOauthLogin(auth, provider);
216
- }, [doOauthLogin, getProviderOptions]);
217
- const twitterLogin = (0, react.useCallback)(() => {
218
- const provider = new _firebase_auth.TwitterAuthProvider();
219
- const options = getProviderOptions("twitter.com");
220
- if (options?.customParameters) provider.setCustomParameters(options.customParameters);
221
- const auth = authRef.current;
222
- if (!auth) throw Error("No auth");
223
- doOauthLogin(auth, provider);
224
- }, [doOauthLogin, getProviderOptions]);
225
- const skipLogin = (0, react.useCallback)(() => {
226
- setLoginSkipped(true);
227
- setLoggedUser(null);
228
- setUserRoles(void 0);
229
- }, []);
230
- return {
231
- user: loggedUser ? {
232
- ...loggedUser,
233
- roles: userRoles,
234
- firebaseUser: loggedUser
235
- } : null,
236
- setUser: updateUser,
237
- setUserRoles,
238
- authProviderError,
239
- authLoading,
240
- initialLoading: loading || initialLoading,
241
- signOut: onSignOut,
242
- getAuthToken,
243
- googleLogin,
244
- skipLogin,
245
- loginSkipped,
246
- emailPasswordLogin,
247
- createUserWithEmailAndPassword,
248
- sendPasswordResetEmail,
249
- fetchSignInMethodsForEmail,
250
- anonymousLogin,
251
- phoneLogin,
252
- appleLogin,
253
- facebookLogin,
254
- githubLogin,
255
- microsoftLogin,
256
- twitterLogin,
257
- confirmationResult,
258
- extra,
259
- setExtra
260
- };
261
- };
262
- //#endregion
263
- //#region src/hooks/useFirebaseStorageSource.ts
264
- /**
265
- * Use this hook to build an {@link StorageSource} based on Firebase storage
266
- * @group Firebase
267
- */
268
- function useFirebaseStorageSource({ firebaseApp, bucketUrl }) {
269
- const projectId = firebaseApp?.options?.projectId;
270
- const urlsCache = {};
271
- return {
272
- putObject({ file, key, metadata, bucket }) {
273
- try {
274
- if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
275
- const storage = (0, _firebase_storage.getStorage)(firebaseApp, bucket ?? bucketUrl);
276
- if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
277
- const uploadTask = (0, _firebase_storage.uploadBytesResumable)((0, _firebase_storage.ref)(storage, key), file, metadata);
278
- return new Promise((resolve, reject) => {
279
- let lastProgress = 0;
280
- let timeoutId = null;
281
- const clearTimeoutIfExists = () => {
282
- if (timeoutId) {
283
- clearTimeout(timeoutId);
284
- timeoutId = null;
285
- }
286
- };
287
- const setProgressTimeout = () => {
288
- clearTimeoutIfExists();
289
- timeoutId = setTimeout(() => {
290
- uploadTask.cancel();
291
- reject(/* @__PURE__ */ new Error(`Upload failed - This is likely a CORS configuration issue. Make sure Firebase Storage is enabled in your project: https://console.firebase.google.com/u/0/project/${projectId}/storage. If it is, check Firebase Storage CORS settings.`));
292
- }, 5e3);
293
- };
294
- setProgressTimeout();
295
- uploadTask.on("state_changed", (entity) => {
296
- const progress = entity.bytesTransferred / entity.totalBytes * 100;
297
- if (progress > lastProgress) {
298
- lastProgress = progress;
299
- setProgressTimeout();
300
- }
301
- }, (error) => {
302
- clearTimeoutIfExists();
303
- console.error("Firebase Storage upload error:", error);
304
- let errorMessage = "Unknown upload error";
305
- if (error?.message) errorMessage = error.message;
306
- else if (typeof error === "string") errorMessage = error;
307
- else if (error?.code) errorMessage = error.code;
308
- if (error?.code === "storage/unauthorized") reject(/* @__PURE__ */ new Error("Unauthorized: Check Firebase Storage security rules"));
309
- else if (error?.code === "storage/canceled") reject(/* @__PURE__ */ new Error("Upload canceled"));
310
- else if (error?.code === "storage/unknown" || !error?.code) reject(/* @__PURE__ */ new Error("Upload failed - Check Firebase Storage CORS configuration or network connection"));
311
- else if (errorMessage.toLowerCase().includes("network")) reject(/* @__PURE__ */ new Error("Network error: Check your internet connection"));
312
- else reject(Object.assign(new Error(errorMessage), { code: error?.code }));
313
- }, () => {
314
- clearTimeoutIfExists();
315
- const fullPath = uploadTask.snapshot.ref.fullPath;
316
- const bucketName = uploadTask.snapshot.ref.bucket;
317
- resolve({
318
- key: fullPath,
319
- bucket: bucketName,
320
- storageUrl: `s3://${bucketName}/${fullPath}`
321
- });
322
- });
323
- });
324
- } catch (error) {
325
- return Promise.reject(error);
326
- }
327
- },
328
- async getObject(path, bucket) {
329
- try {
330
- if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
331
- const storage = (0, _firebase_storage.getStorage)(firebaseApp, bucket ?? bucketUrl);
332
- if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
333
- const url = await (0, _firebase_storage.getDownloadURL)((0, _firebase_storage.ref)(storage, path));
334
- const blob = await (await fetch(url)).blob();
335
- return new File([blob], path);
336
- } catch (e) {
337
- if (typeof e === "object" && e !== null && "code" in e && e.code === "storage/object-not-found") return null;
338
- throw e;
339
- }
340
- },
341
- async getSignedUrl(storagePathOrUrl, bucket) {
342
- if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
343
- let resolvedPathOrUrl = storagePathOrUrl;
344
- let resolvedBucket = bucket;
345
- const match = storagePathOrUrl.match(/^(s3|gs):\/\//);
346
- if (match) {
347
- const protocolLength = match[0].length;
348
- const withoutProtocol = storagePathOrUrl.substring(protocolLength);
349
- const firstSlash = withoutProtocol.indexOf("/");
350
- if (firstSlash > 0) {
351
- resolvedBucket = withoutProtocol.substring(0, firstSlash);
352
- resolvedPathOrUrl = withoutProtocol.substring(firstSlash + 1);
353
- }
354
- }
355
- const storage = (0, _firebase_storage.getStorage)(firebaseApp, resolvedBucket ?? bucketUrl);
356
- if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
357
- if (urlsCache[storagePathOrUrl]) return urlsCache[storagePathOrUrl];
358
- try {
359
- const fileRef = (0, _firebase_storage.ref)(storage, resolvedPathOrUrl);
360
- const [url, metadata] = await Promise.all([(0, _firebase_storage.getDownloadURL)(fileRef), (0, _firebase_storage.getMetadata)(fileRef)]);
361
- const result = {
362
- url,
363
- metadata
364
- };
365
- urlsCache[storagePathOrUrl] = result;
366
- return result;
367
- } catch (e) {
368
- if (typeof e === "object" && e !== null && "code" in e && e.code === "storage/object-not-found") return {
369
- url: null,
370
- fileNotFound: true
371
- };
372
- throw e;
373
- }
374
- },
375
- async listObjects(prefix, options) {
376
- if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
377
- const storage = (0, _firebase_storage.getStorage)(firebaseApp, options?.bucket ?? bucketUrl);
378
- if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
379
- return await (0, _firebase_storage.list)((0, _firebase_storage.ref)(storage, prefix), {
380
- maxResults: options?.maxResults,
381
- pageToken: options?.pageToken
382
- });
383
- },
384
- async deleteObject(path, bucket) {
385
- if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
386
- const storage = (0, _firebase_storage.getStorage)(firebaseApp, bucket ?? bucketUrl);
387
- if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
388
- return (0, _firebase_storage.deleteObject)((0, _firebase_storage.ref)(storage, path));
389
- }
390
- };
391
- }
392
- //#endregion
393
- //#region src/hooks/useInitialiseFirebase.ts
394
- /**
395
- * Function used to initialise Firebase, either by using the provided config,
396
- * or by fetching it by Firebase Hosting, if not specified.
397
- *
398
- * It works as a hook that gives you the loading state and the used
399
- * configuration.
400
- *
401
- * You most likely only need to use this if you are developing a custom app. You can also not use this component
402
- * and initialise Firebase yourself.
403
- *
404
- * @param onFirebaseInit
405
- * @param firebaseConfig
406
- * @param fromUrl
407
- * @param name
408
- * @param authDomain
409
- * @group Firebase
410
- */
411
- function useInitialiseFirebase({ firebaseConfig, fromUrl, onFirebaseInit, name, authDomain }) {
412
- const [firebaseApp, setFirebaseApp] = (0, react.useState)();
413
- const [firebaseConfigLoading, setFirebaseConfigLoading] = (0, react.useState)(false);
414
- const [configError, setConfigError] = (0, react.useState)();
415
- const initFirebase = (0, react.useCallback)((config) => {
416
- if (config.projectId === firebaseApp?.options.projectId) {
417
- console.debug("Firebase app already initialised with the same project ID. This should happen only in development mode.");
418
- setConfigError(void 0);
419
- setFirebaseConfigLoading(false);
420
- return;
421
- }
422
- try {
423
- const targetName = name ?? "[DEFAULT]";
424
- const existingApp = (0, _firebase_app.getApps)().find((app) => app.name === targetName);
425
- if (existingApp) (0, _firebase_app.deleteApp)(existingApp);
426
- const initialisedFirebaseApp = (0, _firebase_app.initializeApp)(config, targetName);
427
- setConfigError(void 0);
428
- setFirebaseConfigLoading(false);
429
- setFirebaseApp(initialisedFirebaseApp);
430
- } catch (e) {
431
- console.error("Error initialising Firebase", e);
432
- setConfigError("It seems like the provided Firebase config is not correct. If you \nare using the credentials provided automatically by Firebase \nHosting, make sure you link your Firebase app to Firebase Hosting. \n\n" + (e instanceof Error ? e.message : JSON.stringify(e)));
433
- }
434
- }, [name]);
435
- (0, react.useEffect)(() => {
436
- if (onFirebaseInit && firebaseConfig && firebaseApp) onFirebaseInit(firebaseConfig, firebaseApp);
437
- }, [firebaseApp]);
438
- (0, react.useEffect)(() => {
439
- setFirebaseConfigLoading(true);
440
- function fetchFromUrl(url) {
441
- fetch(url).then(async (response) => {
442
- console.debug("Firebase init response", response.status);
443
- if (response && response.status < 300) {
444
- const config = await response.json();
445
- if (authDomain) config.authDomain = authDomain;
446
- initFirebase(config);
447
- }
448
- }).catch((e) => {
449
- setFirebaseConfigLoading(false);
450
- setConfigError("Could not load Firebase configuration from Firebase hosting. If the app is not deployed in Firebase hosting, you need to specify the configuration manually" + e.toString());
451
- });
452
- }
453
- if (firebaseConfig) initFirebase(firebaseConfig);
454
- else if (fromUrl) fetchFromUrl(fromUrl);
455
- else if (process.env.NODE_ENV === "production") fetchFromUrl("/__/firebase/init.json");
456
- else {
457
- setFirebaseConfigLoading(false);
458
- setConfigError("You need to deploy the app to Firebase hosting or specify a Firebase configuration object");
459
- }
460
- }, []);
461
- return {
462
- firebaseApp,
463
- firebaseConfigLoading,
464
- configError
465
- };
466
- }
467
- //#endregion
468
- //#region src/hooks/useAppCheck.ts
469
- /**
470
- * Function used to initialise Firebase App Check.
471
- *
472
- * @group Firebase
473
- */
474
- function useAppCheck({ firebaseApp, options }) {
475
- if (options?.debugToken) Object.assign(window, { FIREBASE_APPCHECK_DEBUG_TOKEN: options?.debugToken });
476
- const [appCheckLoading, setAppCheckLoading] = react.default.useState(false);
477
- const [appCheckVerified, setAppCheckVerified] = react.default.useState(void 0);
478
- const [error, setError] = react.default.useState();
479
- const initialCheck = (0, react.useRef)(false);
480
- const verifyToken = (0, react.useCallback)(async (appCheck) => {
481
- console.debug("Verifying App Check token...", appCheck);
482
- try {
483
- const token = await (0, _firebase_app_check.getToken)(appCheck, options?.forceRefresh);
484
- console.debug("App Check token:", token);
485
- if (!token) {
486
- setError("App Check failed.");
487
- setAppCheckVerified(false);
488
- } else {
489
- setAppCheckVerified(true);
490
- console.debug("App Check success.");
491
- }
492
- } catch (e) {
493
- console.error("App Check error:", e);
494
- setError(e instanceof Error ? e.message : String(e));
495
- }
496
- }, [options?.forceRefresh]);
497
- (0, react.useEffect)(() => {
498
- if (!options) return;
499
- if (!firebaseApp) return;
500
- if (appCheckVerified !== void 0) return;
501
- if (initialCheck.current) return;
502
- setAppCheckLoading(true);
503
- const { provider, isTokenAutoRefreshEnabled } = options;
504
- removeCurrentAppCheckDiv();
505
- verifyToken((0, _firebase_app_check.initializeAppCheck)(firebaseApp, {
506
- provider,
507
- isTokenAutoRefreshEnabled
508
- })).then(() => {
509
- setAppCheckLoading(false);
510
- });
511
- initialCheck.current = true;
512
- }, [
513
- appCheckVerified,
514
- firebaseApp,
515
- options,
516
- verifyToken
517
- ]);
518
- return {
519
- loading: appCheckLoading,
520
- appCheckVerified,
521
- error
522
- };
523
- }
524
- function removeCurrentAppCheckDiv() {
525
- const div = document.getElementById("fire_app_check_[DEFAULT]");
526
- if (div) div.remove();
527
- }
528
- //#endregion
529
- //#region src/utils/collections_firestore.ts
530
- function buildCollectionId(idOrPath, parentCollectionSlugs, parentEntityIds) {
531
- if (!parentCollectionSlugs) return (0, _rebasepro_common.stripCollectionPath)(idOrPath);
532
- return [...parentCollectionSlugs.map(_rebasepro_common.stripCollectionPath), (0, _rebasepro_common.stripCollectionPath)(idOrPath)].join(_rebasepro_common.COLLECTION_PATH_SEPARATOR);
533
- }
534
- var docsToCollectionTree = (docs) => {
535
- const collectionsMap = docs.map((doc) => {
536
- const id = doc.id;
537
- const collection = docToCollection(doc);
538
- return { [id]: collection };
539
- }).reduce((a, b) => ({
540
- ...a,
541
- ...b
542
- }), {});
543
- Object.keys(collectionsMap).sort((a, b) => b.split(_rebasepro_common.COLLECTION_PATH_SEPARATOR).length - a.split(_rebasepro_common.COLLECTION_PATH_SEPARATOR).length).forEach((id) => {
544
- const collection = collectionsMap[id];
545
- if (id.includes(_rebasepro_common.COLLECTION_PATH_SEPARATOR)) {
546
- const parentCollection = collectionsMap[id.split(_rebasepro_common.COLLECTION_PATH_SEPARATOR).slice(0, -1).join(_rebasepro_common.COLLECTION_PATH_SEPARATOR)];
547
- if (parentCollection) parentCollection.subcollections = () => [...parentCollection.subcollections?.() ?? [], collection];
548
- delete collectionsMap[id];
549
- }
550
- });
551
- return Object.values(collectionsMap);
552
- };
553
- var docToCollection = (doc) => {
554
- const data = doc.data();
555
- if (!data) throw Error("Entity collection has not been persisted correctly");
556
- const propertiesOrder = data.propertiesOrder;
557
- const sortedProperties = (0, _rebasepro_common.sortProperties)(normalizePropertiesEnumValues(data.properties ?? {}, true), propertiesOrder);
558
- return {
559
- ...data,
560
- properties: sortedProperties,
561
- slug: data.id ?? data.alias ?? data.slug
562
- };
563
- };
564
- /**
565
- * Converts enum values from object format to array format.
566
- * Firestore doesn't preserve object key order, so we must use arrays.
567
- * When enum values are already stored as an array, their order is preserved
568
- * (this is intentional - users can reorder columns in Kanban view).
569
- * Only sort alphabetically when converting from legacy object format.
570
- * @param enumValues - The enum values (object or array format)
571
- * @param sortObjectFormat - If true, sort by id alphabetically when converting from object format
572
- * @returns Array of EnumValueConfig objects
573
- */
574
- function normalizeEnumValuesToArray(enumValues, sortObjectFormat = false) {
575
- if (Array.isArray(enumValues)) return enumValues;
576
- else if (typeof enumValues === "object" && enumValues !== null) {
577
- const entries = Object.entries(enumValues).map(([id, value]) => typeof value === "string" ? {
578
- id,
579
- label: value
580
- } : {
581
- ...value,
582
- id
583
- });
584
- if (sortObjectFormat) entries.sort((a, b) => String(a.id).localeCompare(String(b.id)));
585
- return entries;
586
- }
587
- return [];
588
- }
589
- /**
590
- * Normalizes all enum values in a properties object.
591
- * @param properties - The properties object to normalize
592
- * @param sortObjectFormat - If true, sort enum values alphabetically when converting from object format
593
- * @returns Properties with normalized enum values
594
- */
595
- function normalizePropertiesEnumValues(properties, sortObjectFormat = false) {
596
- const result = {};
597
- Object.entries(properties).forEach(([key, property]) => {
598
- if (typeof property === "object" && property !== null) {
599
- const normalizedProperty = { ...property };
600
- if (normalizedProperty.enum) normalizedProperty.enum = normalizeEnumValuesToArray(normalizedProperty.enum, sortObjectFormat);
601
- const propType = normalizedProperty.type ?? normalizedProperty.dataType;
602
- if (propType === "array" && typeof normalizedProperty.of === "object" && normalizedProperty.of !== null) {
603
- const ofProp = normalizedProperty.of;
604
- if (ofProp.enum) normalizedProperty.of = {
605
- ...ofProp,
606
- enum: normalizeEnumValuesToArray(ofProp.enum, sortObjectFormat)
607
- };
608
- }
609
- if (propType === "map" && normalizedProperty.properties) normalizedProperty.properties = normalizePropertiesEnumValues(normalizedProperty.properties, sortObjectFormat);
610
- result[key] = normalizedProperty;
611
- } else result[key] = property;
612
- });
613
- return result;
614
- }
615
- //#endregion
616
- //#region src/utils/database.ts
617
- async function getFirestoreDataInPath(firebaseApp, path, parentPaths, limit) {
618
- const firestore = (0, _firebase_firestore.getFirestore)(firebaseApp);
619
- if (!parentPaths || parentPaths.length === 0) return (0, _firebase_firestore.getDocs)((0, _firebase_firestore.query)((0, _firebase_firestore.collection)(firestore, path), (0, _firebase_firestore.limit)(limit))).then((queryEntity) => {
620
- return queryEntity.docs.map((doc) => doc.data());
621
- });
622
- else {
623
- let currentDocs = void 0;
624
- let index = 0;
625
- const allPaths = parentPaths;
626
- allPaths.push(path);
627
- let parentPath = allPaths[0];
628
- while (parentPath) {
629
- if (currentDocs) currentDocs = (await Promise.all(currentDocs.map(async (doc) => {
630
- return (await (0, _firebase_firestore.getDocs)((0, _firebase_firestore.query)((0, _firebase_firestore.collection)(firestore, doc.ref.path, parentPath), (0, _firebase_firestore.limit)(5)))).docs;
631
- }))).flat();
632
- else currentDocs = (await (0, _firebase_firestore.getDocs)((0, _firebase_firestore.query)((0, _firebase_firestore.collection)(firestore, parentPath), (0, _firebase_firestore.limit)(5)))).docs;
633
- index++;
634
- parentPath = index < allPaths.length ? allPaths[index] : void 0;
635
- }
636
- return currentDocs ? currentDocs.map((doc) => doc.data()) : [];
637
- }
638
- }
639
- //#endregion
640
- //#region src/utils/algolia.ts
641
- /**
642
- * Utility function to perform a text search in an algolia index,
643
- * returning the ids of the entities.
644
- * @param client The algolia client
645
- * @param indexName
646
- * @param query
647
- * @group Firebase
648
- */
649
- function performAlgoliaTextSearch(client, indexName, query) {
650
- console.debug("Performing Algolia query", client, query);
651
- return client.searchSingleIndex({
652
- indexName,
653
- searchParams: { query }
654
- }).then(({ hits }) => {
655
- return hits.map((hit) => hit.objectID);
656
- }).catch((err) => {
657
- console.error(err);
658
- return [];
659
- });
660
- }
661
- //#endregion
662
- //#region src/utils/pinecone.ts
663
- var DEFAULT_SERVER = "https://api.rebase.pro";
664
- /**
665
- * Utility function to perform a text search in an algolia index,
666
- * returning the ids of the entities.
667
- * @param index
668
- * @param query
669
- * @group Firebase
670
- */
671
- async function performPineconeTextSearch({ host = DEFAULT_SERVER, firebaseToken, projectId, collectionPath, query }) {
672
- console.debug("Performing Pinecone query", collectionPath, query);
673
- return (await (await fetch((host ?? DEFAULT_SERVER) + `/projects/${projectId}/search/${collectionPath}`, {
674
- method: "POST",
675
- headers: {
676
- "Content-Type": "application/json",
677
- Authorization: `Basic ${firebaseToken}`
678
- },
679
- body: JSON.stringify({ query })
680
- })).json()).data.ids;
681
- }
682
- function buildPineconeSearchController({ isPathSupported, search }) {
683
- return (props) => {
684
- const init = (props) => {
685
- return Promise.resolve(isPathSupported(props.path));
686
- };
687
- return {
688
- init,
689
- search
690
- };
691
- };
692
- }
693
- //#endregion
694
- //#region src/utils/text_search_controller.ts
695
- /**
696
- * Utility function to perform a text search in an external index,
697
- * returning the ids of the entities.
698
- * @group Firebase
699
- */
700
- function buildExternalSearchController({ isPathSupported, search }) {
701
- return (props) => {
702
- const init = (props) => {
703
- return Promise.resolve(isPathSupported(props.path));
704
- };
705
- return {
706
- init,
707
- search
708
- };
709
- };
710
- }
711
- //#endregion
712
- //#region src/utils/local_text_search_controller.ts
713
- var MAX_SEARCH_RESULTS = 80;
714
- var localSearchControllerBuilder = ({ firebaseApp }) => {
715
- let currentPath;
716
- const indexes = {};
717
- const listeners = {};
718
- const destroyListener = (path) => {
719
- if (listeners[path]) {
720
- listeners[path]();
721
- delete listeners[path];
722
- delete indexes[path];
723
- }
724
- };
725
- const init = ({ path, collection: collectionProp, databaseId }) => {
726
- if (currentPath && path !== currentPath) destroyListener(currentPath);
727
- currentPath = path;
728
- return new Promise((resolve, reject) => {
729
- if (collectionProp) {
730
- console.debug("Init local search controller", path);
731
- listeners[path] = (0, _firebase_firestore.onSnapshot)((0, _firebase_firestore.query)((0, _firebase_firestore.collection)(databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp), path)), {
732
- next: (entity) => {
733
- if (entity.metadata.fromCache && entity.metadata.hasPendingWrites) return;
734
- const docs = entity.docs.map((doc) => ({
735
- id: doc.id,
736
- ...doc.data()
737
- }));
738
- indexes[path] = buildIndex(docs, collectionProp);
739
- console.debug("Added docs to index", path, docs.length);
740
- resolve(true);
741
- },
742
- error: (e) => {
743
- console.error("Error initializing local search controller", path);
744
- console.error(e);
745
- reject(e);
746
- }
747
- });
748
- }
749
- });
750
- };
751
- const search = async ({ searchString, path }) => {
752
- console.debug("Searching local index", path, searchString);
753
- const index = indexes[path];
754
- if (!index) throw new Error(`Index not found for path ${path}`);
755
- let searchResult = index.search(searchString);
756
- searchResult = searchResult.splice(0, MAX_SEARCH_RESULTS);
757
- searchResult = searchResult.sort((a, b) => {
758
- const aExactMatch = a.item.id === searchString;
759
- const bExactMatch = b.item.id === searchString;
760
- if (aExactMatch && !bExactMatch) return -1;
761
- else if (!aExactMatch && bExactMatch) return 1;
762
- else return (a.score ?? 0) - (b.score ?? 0);
763
- });
764
- return searchResult.map((e) => e.item.id);
765
- };
766
- return {
767
- init,
768
- search
769
- };
770
- };
771
- function buildIndex(list, collection) {
772
- return new fuse_js.default(list, {
773
- threshold: .6,
774
- includeScore: true,
775
- keys: [{
776
- name: "title",
777
- weight: 1
778
- }, ...["id", ...Object.keys(collection.properties)].map((key) => ({
779
- name: key,
780
- weight: .5
781
- }))]
782
- });
783
- }
784
- //#endregion
785
- //#region src/utils/rebase_search_controller.ts
786
- /**
787
- * Creates a text search controller that uses the Rebase Search Extension.
788
- *
789
- * This requires the `rebase-search` extension to be installed in the user's
790
- * Firebase project. The extension automatically deploys Typesense to Cloud Run
791
- * and syncs Firestore data.
792
- *
793
- * @example
794
- * ```typescript
795
- * import { buildRebaseSearchController } from "@rebasepro/firebase";
796
- *
797
- * // Using the extension (recommended)
798
- * const textSearchControllerBuilder = buildRebaseSearchController();
799
- *
800
- * // Or with custom Typesense instance
801
- * const textSearchControllerBuilder = buildRebaseSearchController({
802
- * customConfig: {
803
- * host: "your-typesense-instance.com",
804
- * apiKey: "your-api-key"
805
- * }
806
- * });
807
- *
808
- * <RebaseApp
809
- * textSearchControllerBuilder={textSearchControllerBuilder}
810
- * collections={[
811
- * {
812
- * path: "products",
813
- * name: "Products",
814
- * textSearchEnabled: true, // Enable search for this collection
815
- * properties: { ... }
816
- * }
817
- * ]}
818
- * />
819
- * ```
820
- *
821
- * @param options - Configuration options
822
- * @returns A FirestoreTextSearchControllerBuilder
823
- *
824
- * @group Firebase
825
- */
826
- function buildRebaseSearchController(options) {
827
- const region = options?.region || "us-central1";
828
- const extensionInstanceId = options?.extensionInstanceId || "typesense-search";
829
- let searchConfig = null;
830
- let typesenseClient = null;
831
- let initPromise = null;
832
- return ({ firebaseApp }) => {
833
- /**
834
- * Initializes the Typesense client
835
- */
836
- const initializeClient = async () => {
837
- if (typesenseClient) return;
838
- if (options?.customConfig) searchConfig = {
839
- host: options.customConfig.host,
840
- port: options.customConfig.port || 443,
841
- protocol: options.customConfig.protocol || "https",
842
- apiKey: options.customConfig.apiKey,
843
- path: options.customConfig.path,
844
- collectionsToIndex: ["*"]
845
- };
846
- else {
847
- const getConfig = (0, _firebase_functions.httpsCallable)((0, _firebase_functions.getFunctions)(firebaseApp, region), `ext-${extensionInstanceId}-getSearchConfig`);
848
- try {
849
- searchConfig = (await getConfig()).data;
850
- if (options?.collections && options.collections.length > 0) searchConfig.collectionsToIndex = options.collections;
851
- } catch (error) {
852
- console.error("Failed to get search config from extension:", error);
853
- throw new Error(`Failed to initialize Rebase Search. Make sure the rebase-search extension is installed and configured. Error: ${error instanceof Error ? error.message : String(error)}`);
854
- }
855
- }
856
- if (!searchConfig) throw new Error("Search config not available");
857
- typesenseClient = new (await (import("typesense"))).default.Client({
858
- nodes: [{
859
- host: searchConfig.host,
860
- port: searchConfig.port,
861
- protocol: searchConfig.protocol,
862
- path: searchConfig.path || ""
863
- }],
864
- apiKey: searchConfig.apiKey,
865
- connectionTimeoutSeconds: 5,
866
- retryIntervalSeconds: .5,
867
- numRetries: 2
868
- });
869
- };
870
- /**
871
- * Converts a Firestore path to Typesense collection name
872
- * e.g., "users/123/orders" -> "users_orders"
873
- */
874
- const getTypesenseCollectionName = (path) => {
875
- const pathParts = path.split("/");
876
- const collectionNames = [];
877
- for (let i = 0; i < pathParts.length; i += 2) if (pathParts[i]) collectionNames.push(pathParts[i]);
878
- return collectionNames.join("_");
879
- };
880
- /**
881
- * Extracts parent filter for subcollection queries
882
- * e.g., "users/123/orders" -> { "_parent_users_id": "123" }
883
- */
884
- const getParentFilter = (path) => {
885
- const pathParts = path.split("/");
886
- if (pathParts.length <= 1) return null;
887
- const filters = [];
888
- for (let i = 0; i < pathParts.length - 1; i += 2) {
889
- const collectionName = pathParts[i];
890
- const docId = pathParts[i + 1];
891
- if (collectionName && docId) filters.push(`_parent_${collectionName}_id:=${docId}`);
892
- }
893
- return filters.length > 0 ? filters.join(" && ") : null;
894
- };
895
- /**
896
- * Initializes search for a specific collection path
897
- */
898
- const init = async (props) => {
899
- try {
900
- if (!initPromise) initPromise = initializeClient();
901
- await initPromise;
902
- if (!searchConfig) return false;
903
- const pathParts = props.path.split("/");
904
- const collectionNames = [];
905
- for (let i = 0; i < pathParts.length; i += 2) if (pathParts[i]) collectionNames.push(pathParts[i]);
906
- const collectionPattern = collectionNames.join("/");
907
- const rootCollection = collectionNames[0];
908
- if (searchConfig.collectionsToIndex.includes("*")) return true;
909
- return searchConfig.collectionsToIndex.includes(collectionPattern) || searchConfig.collectionsToIndex.includes(rootCollection);
910
- } catch (error) {
911
- console.error("Failed to initialize Rebase Search:", error);
912
- return false;
913
- }
914
- };
915
- const schemaCache = /* @__PURE__ */ new Map();
916
- /**
917
- * Fetches the Typesense collection schema and returns searchable string field names.
918
- * Results are cached to avoid repeated API calls.
919
- */
920
- const getSearchableFieldsFromSchema = async (collectionName) => {
921
- if (schemaCache.has(collectionName)) return schemaCache.get(collectionName);
922
- try {
923
- const stringFields = (await typesenseClient.collections(collectionName).retrieve()).fields.filter((f) => {
924
- const isStringType = f.type === "string" || f.type === "string[]" || f.type === "string*" || f.type === "auto";
925
- const isNotInternal = !f.name.startsWith("_") && f.name !== ".*";
926
- return isStringType && isNotInternal;
927
- }).map((f) => f.name);
928
- schemaCache.set(collectionName, stringFields);
929
- return stringFields;
930
- } catch (error) {
931
- if (error instanceof Error && "httpStatus" in error && error.httpStatus === 404) throw new Error(`Collection "${collectionName}" not found in Typesense. Make sure the collection has been indexed. Try running the backfill function.`);
932
- throw error;
933
- }
934
- };
935
- /**
936
- * Performs a search and returns document IDs
937
- * Supports subcollections by filtering on parent IDs
938
- */
939
- const search = async (props) => {
940
- if (!typesenseClient) {
941
- if (!initPromise) initPromise = initializeClient();
942
- await initPromise;
943
- }
944
- if (!typesenseClient) throw new Error("Typesense client not initialized. Check extension configuration.");
945
- const collectionName = getTypesenseCollectionName(props.path);
946
- const parentFilter = getParentFilter(props.path);
947
- const searchableFields = await getSearchableFieldsFromSchema(collectionName);
948
- if (searchableFields.length === 0) throw new Error(`No searchable string fields found in Typesense collection "${collectionName}". Make sure some documents have been indexed with string fields.`);
949
- const queryBy = searchableFields.join(",");
950
- try {
951
- const searchParams = {
952
- q: props.searchString,
953
- query_by: queryBy,
954
- per_page: 100,
955
- prefix: true,
956
- typo_tokens_threshold: 1
957
- };
958
- if (parentFilter) searchParams.filter_by = parentFilter;
959
- return (await typesenseClient.collections(collectionName).documents().search(searchParams)).hits?.map((hit) => hit.document.id) ?? [];
960
- } catch (error) {
961
- const message = error instanceof Error ? error.message : String(error);
962
- throw new Error(`Search failed: ${message}`);
963
- }
964
- };
965
- return {
966
- init,
967
- search
968
- };
969
- };
970
- }
971
- //#endregion
972
- //#region src/hooks/useFirestoreDriver.ts
973
- /**
974
- * Use this hook to build a {@link DataDriver} based on Firestore
975
- * @param firebaseApp
976
- * @param textSearchControllerBuilder
977
- * @group Firebase
978
- */
979
- function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestoreIndexesBuilder, localTextSearchEnabled }) {
980
- const searchControllerRef = (0, react.useRef)(void 0);
981
- (0, react.useEffect)(() => {
982
- if (!searchControllerRef.current && firebaseApp) {
983
- if ((textSearchControllerBuilder || localTextSearchEnabled) && !searchControllerRef.current) searchControllerRef.current = buildTextSearchControllerWithLocalSearch({
984
- firebaseApp,
985
- textSearchControllerBuilder,
986
- localTextSearchEnabled: localTextSearchEnabled ?? false
987
- });
988
- }
989
- }, [
990
- firebaseApp,
991
- localTextSearchEnabled,
992
- textSearchControllerBuilder
993
- ]);
994
- const buildQuery = (0, react.useCallback)((path, filter, orderBy, order, startAfter, limit, databaseId) => {
995
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
996
- const firestore = databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp);
997
- const collectionReference = (0, _firebase_firestore.collection)(firestore, path);
998
- const queryParams = [];
999
- if (filter) Object.entries(filter).filter(([_, entry]) => !!entry).forEach(([key, filterParameter]) => {
1000
- const [op, value] = filterParameter;
1001
- if (op === "is-null") {
1002
- queryParams.push((0, _firebase_firestore.where)(key, "==", null));
1003
- return;
1004
- }
1005
- if (op === "is-not-null") {
1006
- queryParams.push((0, _firebase_firestore.where)(key, "!=", null));
1007
- return;
1008
- }
1009
- if (op === "like" || op === "ilike" || op === "not-like" || op === "not-ilike") throw new Error(`Firestore does not support the "${op}" operator (SQL pattern matching). Use a full-text search index or the collection's searchString instead.`);
1010
- queryParams.push((0, _firebase_firestore.where)(key, op, cmsToFirestoreModel(value, firestore)));
1011
- });
1012
- if (orderBy && order) queryParams.push((0, _firebase_firestore.orderBy)(orderBy, order));
1013
- if (startAfter) queryParams.push((0, _firebase_firestore.startAfter)(startAfter));
1014
- if (limit) queryParams.push((0, _firebase_firestore.limit)(limit));
1015
- return (0, _firebase_firestore.query)(collectionReference, ...queryParams);
1016
- }, [firebaseApp]);
1017
- const getAndBuildEntity = (0, react.useCallback)((path, id, databaseId) => {
1018
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1019
- return (0, _firebase_firestore.getDoc)((0, _firebase_firestore.doc)(databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp), path, String(id))).then((docEntity) => {
1020
- if (!docEntity.exists()) return;
1021
- return createRowFromDocument(docEntity);
1022
- });
1023
- }, [firebaseApp]);
1024
- const listenOne = (0, react.useCallback)(({ path, id, collection, onUpdate, onError }) => {
1025
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1026
- const databaseId = collection?.databaseId;
1027
- return (0, _firebase_firestore.onSnapshot)((0, _firebase_firestore.doc)(databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp), path, String(id)), {
1028
- next: (docEntity) => {
1029
- onUpdate(docEntity.exists() ? createRowFromDocument(docEntity) : null);
1030
- },
1031
- error: onError
1032
- });
1033
- }, [firebaseApp]);
1034
- const performTextSearch = (0, react.useCallback)(({ path, databaseId, searchString, onUpdate }) => {
1035
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1036
- const textSearchController = searchControllerRef.current;
1037
- if (!textSearchController) throw Error("Trying to make text search without specifying a FirestoreTextSearchController");
1038
- let subscriptions = [];
1039
- const currentUser = (0, _firebase_auth.getAuth)(firebaseApp)?.currentUser;
1040
- const search = textSearchController.search({
1041
- path,
1042
- searchString,
1043
- currentUser: currentUser ?? void 0,
1044
- databaseId
1045
- });
1046
- if (!search) throw Error("The current path is not supported by the specified FirestoreTextSearchController");
1047
- search.then((ids) => {
1048
- if (!ids || ids.length === 0) {
1049
- subscriptions = [];
1050
- onUpdate([]);
1051
- }
1052
- const rows = [];
1053
- const addedEntitiesSet = /* @__PURE__ */ new Set();
1054
- subscriptions = (ids ?? []).map((id) => {
1055
- return listenOne({
1056
- path,
1057
- id,
1058
- onUpdate: (row) => {
1059
- const incomingId = row?.id;
1060
- if (row && incomingId !== void 0) {
1061
- if (!addedEntitiesSet.has(incomingId)) {
1062
- addedEntitiesSet.add(incomingId);
1063
- rows.push(row);
1064
- onUpdate(rows);
1065
- }
1066
- } else {
1067
- addedEntitiesSet.delete(id);
1068
- onUpdate([...rows.filter((r) => r.id !== id)]);
1069
- }
1070
- }
1071
- });
1072
- });
1073
- });
1074
- return () => {
1075
- subscriptions.forEach((p) => p());
1076
- };
1077
- }, [firebaseApp, listenOne]);
1078
- const initTextSearch = (0, react.useCallback)(async (props) => {
1079
- console.debug("Init text search controller", searchControllerRef.current, props.path);
1080
- if (!searchControllerRef.current) {
1081
- console.warn("You are trying to use text search, but have not provided a text search controller in `useFirestoreDriver`. You can also set the flag `localTextSearchEnabled` to use local search in `useFirestoreDriver`. Local text search can incur in performance issues and higher costs for large datasets.");
1082
- return false;
1083
- }
1084
- try {
1085
- return searchControllerRef.current.init(props);
1086
- } catch (e) {
1087
- console.error("Error initializing text search controller", e);
1088
- return false;
1089
- }
1090
- }, []);
1091
- /**
1092
- * Fetch entities in a Firestore path
1093
- * @param path
1094
- * @param collection
1095
- * @param filter
1096
- * @param limit
1097
- * @param startAfter
1098
- * @param searchString
1099
- * @param orderBy
1100
- * @param order
1101
- * @return Function to cancel subscription
1102
- * @see useCollection if you need this functionality implemented as a hook
1103
- * @group Firestore
1104
- */
1105
- const fetchCollection = (0, react.useCallback)(async ({ path, filter, limit, startAfter, searchString, orderBy, order, collection }) => {
1106
- const databaseId = collection?.databaseId;
1107
- const resolvedPath = path;
1108
- console.debug("Fetching collection", {
1109
- path,
1110
- limit,
1111
- filter,
1112
- startAfter,
1113
- orderBy,
1114
- order
1115
- });
1116
- return (await (0, _firebase_firestore.getDocs)(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId))).docs.map((doc) => createRowFromDocument(doc));
1117
- }, [buildQuery]);
1118
- /**
1119
- * Listen to a entities in a given path
1120
- * @param path
1121
- * @param collection
1122
- * @param onError
1123
- * @param filter
1124
- * @param limit
1125
- * @param startAfter
1126
- * @param searchString
1127
- * @param orderBy
1128
- * @param order
1129
- * @param onUpdate
1130
- * @return Function to cancel subscription
1131
- * @see useCollection if you need this functionality implemented as a hook
1132
- * @group Firestore
1133
- */
1134
- const listenCollection = (0, react.useCallback)(({ path, filter, limit, startAfter, searchString, orderBy, order, onUpdate, onError, collection }) => {
1135
- console.debug("Listening collection", {
1136
- path,
1137
- searchString,
1138
- limit,
1139
- filter,
1140
- startAfter,
1141
- orderBy,
1142
- order,
1143
- collection
1144
- });
1145
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1146
- const databaseId = collection?.databaseId;
1147
- if (searchString) return performTextSearch({
1148
- path,
1149
- searchString,
1150
- onUpdate,
1151
- databaseId
1152
- });
1153
- const resolvedPath = path;
1154
- console.debug("Resolved path for listening", {
1155
- path,
1156
- resolvedPath
1157
- });
1158
- return (0, _firebase_firestore.onSnapshot)(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId), {
1159
- next: (entity) => {
1160
- if (!searchString) onUpdate(entity.docs.map((doc) => createRowFromDocument(doc)));
1161
- },
1162
- error: onError
1163
- });
1164
- }, [
1165
- buildQuery,
1166
- firebaseApp,
1167
- performTextSearch
1168
- ]);
1169
- /**
1170
- * Retrieve a entity given a path and a collection
1171
- * @param path
1172
- * @param id
1173
- * @param collection
1174
- * @group Firestore
1175
- */
1176
- const fetchOne = (0, react.useCallback)(({ path, id, collection }) => {
1177
- return getAndBuildEntity(path, id, collection?.databaseId);
1178
- }, [getAndBuildEntity]);
1179
- /**
1180
- * Save entity to the specified path. Note that Firestore does not allow
1181
- * undefined values.
1182
- * @param path
1183
- * @param id
1184
- * @param values
1185
- * @param schemaId
1186
- * @param collection
1187
- * @param status
1188
- * @group Firestore
1189
- */
1190
- const save = (0, react.useCallback)(({ path, id, values: valuesProp, collection, status }) => {
1191
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1192
- console.debug("1", {
1193
- path,
1194
- id,
1195
- values: valuesProp,
1196
- collection
1197
- });
1198
- const values = cmsToFirestoreModel(valuesProp, (0, _firebase_firestore.getFirestore)(firebaseApp));
1199
- console.debug("2", {
1200
- path,
1201
- id,
1202
- values: valuesProp,
1203
- collection
1204
- });
1205
- const databaseId = collection?.databaseId;
1206
- const collectionReference = (0, _firebase_firestore.collection)(databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp), path);
1207
- console.debug("Saving entity", {
1208
- path,
1209
- id,
1210
- values,
1211
- databaseId
1212
- });
1213
- let documentReference;
1214
- if (id) documentReference = (0, _firebase_firestore.doc)(collectionReference, String(id));
1215
- else documentReference = (0, _firebase_firestore.doc)(collectionReference);
1216
- return (0, _firebase_firestore.setDoc)(documentReference, values, { merge: true }).then(() => {
1217
- return {
1218
- ...firestoreToCMSModel(values),
1219
- id: documentReference.id
1220
- };
1221
- }).catch((error) => {
1222
- console.error("Error saving entity", error);
1223
- throw error;
1224
- });
1225
- }, [firebaseApp]);
1226
- /**
1227
- * Delete a entity
1228
- * @param entity
1229
- * @param collection
1230
- * @group Firestore
1231
- */
1232
- const deleteOne = (0, react.useCallback)(({ row, collection }) => {
1233
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1234
- const databaseId = collection?.databaseId;
1235
- return (0, _firebase_firestore.deleteDoc)((0, _firebase_firestore.doc)(databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp), row.path, String(row.id)));
1236
- }, [firebaseApp]);
1237
- /**
1238
- * Check if the given property is unique in the given collection
1239
- * @param path Collection path
1240
- * @param name of the property
1241
- * @param value
1242
- * @param property
1243
- * @param id
1244
- * @return `true` if there are no other fields besides the given entity
1245
- * @group Firestore
1246
- */
1247
- const checkUniqueField = (0, react.useCallback)(async (path, name, value, id, collection) => {
1248
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1249
- const databaseId = collection?.databaseId;
1250
- const firestore = databaseId ? (0, _firebase_firestore.getFirestore)(firebaseApp, databaseId) : (0, _firebase_firestore.getFirestore)(firebaseApp);
1251
- if (value === void 0 || value === null) return Promise.resolve(true);
1252
- return (await (0, _firebase_firestore.getDocs)((0, _firebase_firestore.query)((0, _firebase_firestore.collection)(firestore, path), (0, _firebase_firestore.where)(name, "==", cmsToFirestoreModel(value, firestore))))).docs.filter((doc) => doc.id !== id).length === 0;
1253
- }, [firebaseApp]);
1254
- const count = (0, react.useCallback)(async ({ path, filter, order, orderBy, collection }) => {
1255
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1256
- const databaseId = collection?.databaseId;
1257
- return (await (0, _firebase_firestore.getCountFromServer)(buildQuery(path, filter, orderBy, order, void 0, void 0, databaseId))).data().count;
1258
- }, [firebaseApp]);
1259
- const isFilterCombinationValid = (0, react.useCallback)(({ path, collection, filterValues, sortBy }) => {
1260
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1261
- if (firestoreIndexesBuilder === void 0) return true;
1262
- const indexes = firestoreIndexesBuilder?.({
1263
- path,
1264
- collection
1265
- });
1266
- const sortKey = sortBy ? sortBy[0] : void 0;
1267
- const sortDirection = sortBy ? sortBy[1] : void 0;
1268
- const values = Object.values(filterValues);
1269
- const filterKeys = Object.keys(filterValues);
1270
- const filtersCount = filterKeys.length;
1271
- if (!sortKey && values.every((v) => v[0] === "==")) return true;
1272
- if (filtersCount === 1 && (!sortKey || sortKey === filterKeys[0])) return true;
1273
- if (!indexes && filtersCount > 1) return false;
1274
- return !!indexes && indexes.filter((compositeIndex) => !sortKey || sortKey in compositeIndex).find((compositeIndex) => Object.entries(filterValues).every(([key, value]) => compositeIndex[key] !== void 0 && (!sortDirection || compositeIndex[key] === sortDirection))) !== void 0;
1275
- }, [firebaseApp]);
1276
- return (0, react.useMemo)(() => ({
1277
- key: "firestore",
1278
- currentTime,
1279
- initialised: Boolean(firebaseApp),
1280
- initTextSearch,
1281
- fetchCollection,
1282
- listenCollection,
1283
- fetchOne,
1284
- listenOne,
1285
- save,
1286
- delete: deleteOne,
1287
- checkUniqueField,
1288
- count,
1289
- isFilterCombinationValid
1290
- }), [
1291
- firebaseApp,
1292
- initTextSearch,
1293
- fetchCollection,
1294
- listenCollection,
1295
- fetchOne,
1296
- listenOne,
1297
- save,
1298
- deleteOne,
1299
- checkUniqueField,
1300
- count,
1301
- isFilterCombinationValid
1302
- ]);
1303
- }
1304
- var createRowFromDocument = (docSnap) => {
1305
- return {
1306
- ...firestoreToCMSModel(docSnap.data()),
1307
- id: docSnap.id
1308
- };
1309
- };
1310
- /**
1311
- * Recursive function that converts Firestore data types into CMS or plain
1312
- * JS types.
1313
- * Rebase uses Javascript dates internally instead of Firestore timestamps.
1314
- * This makes it easier to interact with the rest of the libraries and
1315
- * bindings.
1316
- * Also, Firestore references are replaced with {@link EntityReference}
1317
- * @param data
1318
- * @group Firestore
1319
- */
1320
- function firestoreToCMSModel(data) {
1321
- if (data === null || data === void 0) return null;
1322
- if (typeof data === "object" && data !== null && "isEqual" in data && typeof data.isEqual === "function" && (0, _firebase_firestore.deleteField)().isEqual(data)) return;
1323
- if (typeof data === "object" && data !== null && "isEqual" in data && typeof data.isEqual === "function" && (0, _firebase_firestore.serverTimestamp)().isEqual(data)) return null;
1324
- if (data instanceof _firebase_firestore.Timestamp || typeof data === "object" && data !== null && "toDate" in data && typeof data.toDate === "function" && data.toDate() instanceof Date) return data.toDate();
1325
- if (data instanceof Date) return data;
1326
- if (typeof data === "object" && "__type__" in data && data.__type__ === "__vector__") return data;
1327
- if (data instanceof _firebase_firestore.VectorValue || typeof data === "object" && data !== null && "toArray" in data && typeof data.toArray === "function" && data.constructor?.name === "VectorValue") return {
1328
- __type__: "__vector__",
1329
- value: data.toArray()
1330
- };
1331
- if (data instanceof _firebase_firestore.GeoPoint) return new _rebasepro_types.GeoPoint(data.latitude, data.longitude);
1332
- if (data instanceof _firebase_firestore.DocumentReference) {
1333
- const databaseId = (data?.firestore)?._databaseId?.database;
1334
- return new _rebasepro_types.EntityReference({
1335
- id: data.id,
1336
- path: getCMSPathFromFirestorePath(data.path),
1337
- databaseId
1338
- });
1339
- }
1340
- if (Array.isArray(data)) return data.map(firestoreToCMSModel).filter((v) => v !== void 0);
1341
- if (typeof data === "object") {
1342
- const result = {};
1343
- for (const key of Object.keys(data)) {
1344
- const childValue = firestoreToCMSModel(data[key]);
1345
- if (childValue !== void 0) result[key] = childValue;
1346
- }
1347
- return result;
1348
- }
1349
- return data;
1350
- }
1351
- /**
1352
- * Remove id from Firestore path
1353
- * @param fsPath
1354
- */
1355
- function getCMSPathFromFirestorePath(fsPath) {
1356
- let to = fsPath.lastIndexOf("/");
1357
- to = to === -1 ? fsPath.length : to;
1358
- return fsPath.substring(0, to);
1359
- }
1360
- function cmsToFirestoreModel(data, firestore, inArray = false) {
1361
- if (data === void 0) return (0, _firebase_firestore.deleteField)();
1362
- else if (data === null) return null;
1363
- else if (Array.isArray(data)) return data.filter((v) => v !== void 0).map((v) => cmsToFirestoreModel(v, firestore, true));
1364
- else if (typeof data === "object" && data !== null && "isEntityReference" in data && typeof data.isEntityReference === "function" && data.isEntityReference()) {
1365
- const entityRef = data;
1366
- return (0, _firebase_firestore.doc)(entityRef.databaseId ? (0, _firebase_firestore.getFirestore)(firestore.app, entityRef.databaseId) : firestore, entityRef.path, entityRef.id);
1367
- } else if (data && typeof data === "object" && "__type" in data && data.__type === "relation" && "path" in data && "id" in data) {
1368
- const rel = data;
1369
- return (0, _firebase_firestore.doc)(firestore, rel.path, String(rel.id));
1370
- } else if (data instanceof _rebasepro_types.GeoPoint) return new _firebase_firestore.GeoPoint(data.latitude, data.longitude);
1371
- else if (data instanceof Date) return _firebase_firestore.Timestamp.fromDate(data);
1372
- else if (data && typeof data === "object" && "__type__" in data && data.__type__ === "__vector__") return (0, _firebase_firestore.vector)(data.value || []);
1373
- else if (data && typeof data === "object") return Object.entries(data).map(([key, v]) => {
1374
- const firestoreModel = cmsToFirestoreModel(v, firestore);
1375
- if (firestoreModel !== void 0) return { [key]: firestoreModel };
1376
- else return {};
1377
- }).reduce((a, b) => ({
1378
- ...a,
1379
- ...b
1380
- }), {});
1381
- return data;
1382
- }
1383
- function currentTime() {
1384
- return (0, _firebase_firestore.serverTimestamp)();
1385
- }
1386
- function buildTextSearchControllerWithLocalSearch({ textSearchControllerBuilder, firebaseApp, localTextSearchEnabled }) {
1387
- if (!textSearchControllerBuilder && localTextSearchEnabled) {
1388
- console.debug("Using local search only");
1389
- return localSearchControllerBuilder({ firebaseApp });
1390
- }
1391
- if (!localTextSearchEnabled && textSearchControllerBuilder) {
1392
- console.debug("Using external text search only");
1393
- return textSearchControllerBuilder({ firebaseApp });
1394
- }
1395
- if (!textSearchControllerBuilder && !localTextSearchEnabled) return;
1396
- const localSearchController = localSearchControllerBuilder({ firebaseApp });
1397
- const textSearchController = textSearchControllerBuilder({ firebaseApp });
1398
- return {
1399
- init: async (props) => {
1400
- if (await textSearchController.init(props)) {
1401
- console.debug("External Text search controller supports path", props.path);
1402
- return true;
1403
- }
1404
- if (localTextSearchEnabled) return localSearchController.init(props);
1405
- return false;
1406
- },
1407
- search: async (props) => {
1408
- return await textSearchController.search(props) ?? await localSearchController.search(props);
1409
- }
1410
- };
1411
- }
1412
- //#endregion
1413
- //#region src/hooks/useFirebaseRealTimeDBDelegate.ts
1414
- function useFirebaseRTDBDelegate({ firebaseApp }) {
1415
- return {
1416
- key: "firebase_rtdb",
1417
- fetchCollection: (0, react.useCallback)(async ({ path, filter, limit, startAfter, orderBy, order, searchString }) => {
1418
- if (!firebaseApp) throw new Error("Firebase app not provided");
1419
- let dbQuery = (0, _firebase_database.query)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), path));
1420
- if (startAfter !== void 0) dbQuery = (0, _firebase_database.query)(dbQuery, (0, _firebase_database.orderByKey)(), (0, _firebase_database.startAt)(String(startAfter)));
1421
- if (limit !== void 0) dbQuery = (0, _firebase_database.query)(dbQuery, (0, _firebase_database.limitToFirst)(limit));
1422
- const entity = await (0, _firebase_database.get)(dbQuery);
1423
- if (entity.exists()) return Object.entries(entity.val()).map(([id, values]) => ({
1424
- ...delegateToCMSModel(values),
1425
- id
1426
- }));
1427
- return [];
1428
- }, [firebaseApp]),
1429
- listenCollection: (0, react.useCallback)(({ path, onUpdate }) => {
1430
- if (!firebaseApp) throw new Error("Firebase app not provided");
1431
- const unsubscribe = (0, _firebase_database.onValue)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), path), (entity) => {
1432
- if (entity.exists()) onUpdate(Object.entries(entity.val()).map(([id, values]) => ({
1433
- ...delegateToCMSModel(values),
1434
- id
1435
- })));
1436
- else onUpdate([]);
1437
- });
1438
- return () => unsubscribe();
1439
- }, [firebaseApp]),
1440
- fetchOne: (0, react.useCallback)(async ({ path, id }) => {
1441
- if (!firebaseApp) throw new Error("Firebase app not provided");
1442
- const entity = await (0, _firebase_database.get)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), `${path}/${id}`));
1443
- if (entity.exists()) return {
1444
- ...delegateToCMSModel(entity.val()),
1445
- id
1446
- };
1447
- }, [firebaseApp]),
1448
- listenOne: (0, react.useCallback)(({ path, id, onUpdate, onError }) => {
1449
- if (!firebaseApp) throw new Error("Firebase app not provided");
1450
- const unsubscribe = (0, _firebase_database.onValue)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), `${path}/${id}`), (entity) => {
1451
- if (entity.exists()) onUpdate({
1452
- ...delegateToCMSModel(entity.val()),
1453
- id
1454
- });
1455
- else onError?.(/* @__PURE__ */ new Error("Entity does not exist"));
1456
- });
1457
- return () => unsubscribe();
1458
- }, [firebaseApp]),
1459
- save: (0, react.useCallback)(async ({ path, id, values }) => {
1460
- if (!firebaseApp) throw new Error("Firebase app not provided");
1461
- const database = (0, _firebase_database.getDatabase)(firebaseApp);
1462
- const finalId = id ?? (0, _firebase_database.push)((0, _firebase_database.ref)(database, path)).key;
1463
- if (!finalId) throw new Error("Could not generate a new id");
1464
- const transformedValues = cmsToRTDBModel(values, database);
1465
- await (0, _firebase_database.set)((0, _firebase_database.ref)(database, `${path}/${finalId}`), transformedValues);
1466
- return {
1467
- ...values,
1468
- id: finalId
1469
- };
1470
- }, [firebaseApp]),
1471
- delete: (0, react.useCallback)(async ({ row }) => {
1472
- if (!firebaseApp) throw new Error("Firebase app not provided");
1473
- await (0, _firebase_database.remove)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), `${row.path}/${row.id}`));
1474
- }, [firebaseApp]),
1475
- checkUniqueField: (0, react.useCallback)(async (slug, name, value, id) => {
1476
- if (!firebaseApp) throw new Error("Firebase app not provided");
1477
- const entity = await (0, _firebase_database.get)((0, _firebase_database.query)((0, _firebase_database.ref)((0, _firebase_database.getDatabase)(firebaseApp), slug), (0, _firebase_database.orderByChild)(name), (0, _firebase_database.startAt)(value), (0, _firebase_database.limitToFirst)(1)));
1478
- if (!entity.exists()) return true;
1479
- const [key, entityValue] = Object.entries(entity.val())[0];
1480
- if (entityValue && typeof entityValue === "object" && entityValue[name] === value && key === id) return true;
1481
- return false;
1482
- }, [firebaseApp]),
1483
- isFilterCombinationValid: (0, react.useCallback)(({ path, filter, sortBy }) => {
1484
- return false;
1485
- }, []),
1486
- currentTime: () => /* @__PURE__ */ new Date()
1487
- };
1488
- }
1489
- /**
1490
- * Transform data from RTDB format back to CMS format
1491
- * This is used internally when fetching/listening to data
1492
- */
1493
- function delegateToCMSModel(data) {
1494
- if (data === null || data === void 0) return null;
1495
- if (Array.isArray(data)) return data.map(delegateToCMSModel).filter((v) => v !== void 0);
1496
- if (typeof data === "object") {
1497
- const result = {};
1498
- for (const key of Object.keys(data)) {
1499
- const childValue = delegateToCMSModel(data[key]);
1500
- if (childValue !== void 0) result[key] = childValue;
1501
- }
1502
- return result;
1503
- }
1504
- return data;
1505
- }
1506
- /**
1507
- * Transform data from CMS format to RTDB format
1508
- * This is used internally when saving data
1509
- */
1510
- function cmsToRTDBModel(data, database) {
1511
- if (data === void 0) return null;
1512
- else if (data === null) return null;
1513
- else if (Array.isArray(data)) return data.filter((v) => v !== void 0).map((v) => cmsToRTDBModel(v, database));
1514
- else if (typeof data === "object" && data !== null && "isEntityReference" in data && typeof data.isEntityReference === "function" && data.isEntityReference()) {
1515
- const entityRef = data;
1516
- return (0, _firebase_database.ref)(database, `${entityRef.slug}/${entityRef.id}`);
1517
- } else if (data instanceof Date) return data.toISOString();
1518
- else if (data && typeof data === "object") return Object.entries(data).map(([key, v]) => {
1519
- const rtdbModel = cmsToRTDBModel(v, database);
1520
- if (rtdbModel !== void 0) return { [key]: rtdbModel };
1521
- else return {};
1522
- }).reduce((a, b) => ({
1523
- ...a,
1524
- ...b
1525
- }), {});
1526
- return data;
1527
- }
1528
- //#endregion
1529
- //#region src/hooks/useRecaptcha.tsx
1530
- var RECAPTCHA_CONTAINER_ID = "recaptcha-container";
1531
- function useRecaptcha() {
1532
- (0, react.useEffect)(() => {
1533
- if (!window || window?.recaptchaVerifier) return;
1534
- const auth = (0, _firebase_auth.getAuth)();
1535
- window.recaptchaVerifier = new _firebase_auth.RecaptchaVerifier(auth, RECAPTCHA_CONTAINER_ID, { size: "invisible" });
1536
- }, []);
1537
- return null;
1538
- }
1539
- //#endregion
1540
- //#region src/hooks/useBuildUserManagement.tsx
1541
- /**
1542
- * This hook is used to build a user management object that can be used to
1543
- * manage users and roles in a Firestore backend.
1544
- * @param authController
1545
- * @param dataSourceDelegate
1546
- * @param usersPath
1547
- * @param rolesPath
1548
- * @param roles
1549
- * @param allowDefaultRolesCreation
1550
- */
1551
- function useBuildUserManagement({ authController, dataSourceDelegate, roles: rolesProp, usersPath = "__FIRECMS/config/users", rolesPath = "__FIRECMS/config/roles", allowDefaultRolesCreation }) {
1552
- if (!authController) throw Error("useBuildUserManagement: You need to provide an authController since version 3.0.0-beta.11. Check https://firecms.co/docs/pro/migrating_from_v3_beta");
1553
- const rolesDefinedInCode = (rolesProp ?? [])?.length > 0;
1554
- const [rolesLoading, setRolesLoading] = react.default.useState(!rolesDefinedInCode);
1555
- const [usersLoading, setUsersLoading] = react.default.useState(true);
1556
- const [roles, setRoles] = react.default.useState(rolesProp ?? []);
1557
- const [usersWithRoleIds, setUsersWithRoleIds] = react.default.useState([]);
1558
- const users = usersWithRoleIds.map((u) => ({ ...u }));
1559
- const [rolesError, setRolesError] = react.default.useState();
1560
- const [usersError, setUsersError] = react.default.useState();
1561
- const loading = rolesLoading || usersLoading;
1562
- (0, react.useEffect)(() => {
1563
- if (rolesDefinedInCode) return;
1564
- if (!dataSourceDelegate || !rolesPath) return;
1565
- if (dataSourceDelegate.initialised !== void 0 && !dataSourceDelegate.initialised) return;
1566
- if (authController?.initialLoading) return;
1567
- setRolesLoading(true);
1568
- return dataSourceDelegate.listenCollection?.({
1569
- path: rolesPath,
1570
- onUpdate(rows) {
1571
- setRolesError(void 0);
1572
- console.debug("Updating roles", rows);
1573
- try {
1574
- const newRoles = rowsToRoles(rows);
1575
- if (!(0, fast_equals.deepEqual)(newRoles, roles)) setRoles(newRoles);
1576
- } catch (e) {
1577
- setRoles([]);
1578
- console.error("Error loading roles", e);
1579
- setRolesError(e);
1580
- }
1581
- setRolesLoading(false);
1582
- },
1583
- onError(e) {
1584
- setRoles([]);
1585
- console.error("Error loading roles", e);
1586
- setRolesError(e instanceof Error ? e : new Error(String(e)));
1587
- setRolesLoading(false);
1588
- }
1589
- });
1590
- }, [
1591
- rolesDefinedInCode,
1592
- dataSourceDelegate?.initialised,
1593
- authController?.initialLoading,
1594
- authController?.user?.uid,
1595
- rolesPath
1596
- ]);
1597
- (0, react.useEffect)(() => {
1598
- if (!dataSourceDelegate || !usersPath) return;
1599
- if (dataSourceDelegate.initialised !== void 0 && !dataSourceDelegate.initialised) return;
1600
- if (authController?.initialLoading) return;
1601
- setUsersLoading(true);
1602
- return dataSourceDelegate.listenCollection?.({
1603
- path: usersPath,
1604
- onUpdate(rows) {
1605
- console.debug("Updating users", rows);
1606
- setUsersError(void 0);
1607
- try {
1608
- setUsersWithRoleIds(rowsToUsers(rows));
1609
- } catch (e) {
1610
- setUsersWithRoleIds([]);
1611
- console.error("Error loading users", e);
1612
- setUsersError(e);
1613
- }
1614
- setUsersLoading(false);
1615
- },
1616
- onError(e) {
1617
- console.error("Error loading users", e);
1618
- setUsersWithRoleIds([]);
1619
- setUsersError(e instanceof Error ? e : new Error(String(e)));
1620
- setUsersLoading(false);
1621
- }
1622
- });
1623
- }, [
1624
- dataSourceDelegate?.initialised,
1625
- authController?.initialLoading,
1626
- authController?.user?.uid,
1627
- usersPath
1628
- ]);
1629
- const saveUser = (0, react.useCallback)(async (user) => {
1630
- if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
1631
- if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
1632
- console.debug("Persisting user", user);
1633
- const roleIds = user.roles;
1634
- const email = user.email?.toLowerCase().trim();
1635
- if (!email) throw Error("Email is required");
1636
- const userExists = users.find((u) => u.email?.toLowerCase() === email);
1637
- const data = {
1638
- ...user,
1639
- roles: roleIds ?? [],
1640
- ...userExists ? {} : { created_on: /* @__PURE__ */ new Date() }
1641
- };
1642
- if (userExists && userExists.uid !== user.uid) {
1643
- const row = {
1644
- values: {},
1645
- path: usersPath,
1646
- id: userExists.uid
1647
- };
1648
- await dataSourceDelegate.delete({ row }).then(() => {
1649
- console.debug("Deleted previous user", userExists);
1650
- }).catch((e) => {
1651
- console.error("Error deleting user", e);
1652
- });
1653
- }
1654
- return dataSourceDelegate.save({
1655
- status: "existing",
1656
- path: usersPath,
1657
- id: email,
1658
- values: (0, _rebasepro_utils.removeUndefined)(data)
1659
- }).then(() => user);
1660
- }, [usersPath, dataSourceDelegate?.initialised]);
1661
- const saveRole = (0, react.useCallback)((role) => {
1662
- if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
1663
- if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
1664
- console.debug("Persisting role", role);
1665
- const { id, ...roleData } = role;
1666
- return dataSourceDelegate.save({
1667
- status: "existing",
1668
- path: rolesPath,
1669
- id,
1670
- values: (0, _rebasepro_utils.removeUndefined)(roleData)
1671
- }).then(() => {});
1672
- }, [rolesPath, dataSourceDelegate?.initialised]);
1673
- const deleteUser = (0, react.useCallback)(async (user) => {
1674
- if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
1675
- if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
1676
- console.debug("Deleting", user);
1677
- const { uid } = user;
1678
- const row = {
1679
- path: usersPath,
1680
- id: uid,
1681
- values: {}
1682
- };
1683
- await dataSourceDelegate.delete({ row });
1684
- }, [usersPath, dataSourceDelegate?.initialised]);
1685
- const deleteRole = (0, react.useCallback)(async (role) => {
1686
- if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
1687
- if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
1688
- console.debug("Deleting", role);
1689
- const { id } = role;
1690
- const row = {
1691
- path: rolesPath,
1692
- id,
1693
- values: {}
1694
- };
1695
- await dataSourceDelegate.delete({ row });
1696
- }, [rolesPath, dataSourceDelegate?.initialised]);
1697
- const defineRolesFor = (0, react.useCallback)((user) => {
1698
- if (!usersWithRoleIds) throw Error("Users not loaded");
1699
- const mgmtUser = usersWithRoleIds.find((u) => u.email?.toLowerCase() === user?.email?.toLowerCase());
1700
- if (!mgmtUser || !mgmtUser.roles) return void 0;
1701
- return mgmtUser.roles;
1702
- }, [usersWithRoleIds]);
1703
- const accessGate = (0, react.useCallback)(({ user }) => {
1704
- if (loading) return false;
1705
- if (user === null) {
1706
- console.warn("User is null, returning");
1707
- return false;
1708
- }
1709
- if (users.length === 0) {
1710
- console.warn("No users created yet");
1711
- return true;
1712
- }
1713
- const mgmtUser = users.find((u) => u.email?.toLowerCase() === user?.email?.toLowerCase());
1714
- if (mgmtUser) {
1715
- const needsUidUpdate = mgmtUser.uid !== user.uid;
1716
- const needsPhotoUpdate = user.photoURL && mgmtUser.photoURL !== user.photoURL;
1717
- if (needsUidUpdate || needsPhotoUpdate) {
1718
- console.debug(`User ${needsUidUpdate ? "uid" : "photoURL"} has changed, updating user in user management system`);
1719
- saveUser({
1720
- ...mgmtUser,
1721
- uid: user.uid,
1722
- ...needsPhotoUpdate ? { photoURL: user.photoURL } : {}
1723
- }).then(() => {
1724
- console.debug("User updated in user management system", mgmtUser);
1725
- }).catch((e) => {
1726
- console.error("Error updating user in user management system", e);
1727
- });
1728
- }
1729
- console.debug("User found in user management system", mgmtUser);
1730
- return true;
1731
- }
1732
- throw Error("Could not find a user with the provided email in the user management system.");
1733
- }, [loading, users]);
1734
- const userRoles = authController.user ? defineRolesFor(authController.user) : void 0;
1735
- const isAdmin = (userRoles ?? []).some((r) => r === "admin");
1736
- (0, react.useEffect)(() => {
1737
- console.debug("Setting user roles", {
1738
- userRoles,
1739
- roles
1740
- });
1741
- authController.setUserRoles?.(userRoles ?? []);
1742
- }, [userRoles]);
1743
- const getUser = (0, react.useCallback)((uid) => {
1744
- if (!users) return null;
1745
- return users.find((u) => u.uid === uid) ?? null;
1746
- }, [users]);
1747
- return {
1748
- loading,
1749
- roles,
1750
- users,
1751
- saveUser,
1752
- saveRole,
1753
- rolesError,
1754
- deleteUser,
1755
- deleteRole,
1756
- usersError,
1757
- isAdmin,
1758
- allowDefaultRolesCreation: allowDefaultRolesCreation === void 0 ? true : allowDefaultRolesCreation,
1759
- defineRolesFor,
1760
- accessGate,
1761
- ...authController,
1762
- initialLoading: authController.initialLoading || loading,
1763
- userRoles,
1764
- getUser,
1765
- user: authController.user ? {
1766
- ...authController.user,
1767
- roles: userRoles
1768
- } : null
1769
- };
1770
- }
1771
- var rowsToUsers = (rows) => {
1772
- return rows.map((row) => {
1773
- const { id, ...data } = row;
1774
- return {
1775
- ...data,
1776
- uid: id,
1777
- created_on: data.created_on,
1778
- updated_on: data.updated_on
1779
- };
1780
- });
1781
- };
1782
- var rowsToRoles = (rows) => {
1783
- return rows.map((row) => ({
1784
- ...row,
1785
- id: row.id
1786
- }));
1787
- };
1788
- //#endregion
1789
- //#region src/hooks/useFirebaseAccessGate.tsx
1790
- /**
1791
- * Hook that evaluates a {@link FirebaseAccessGate} callback after
1792
- * the user logs in and gates access to the main CMS view.
1793
- *
1794
- * @group Firebase
1795
- */
1796
- function useFirebaseAccessGate({ disabled, authController, accessGate, storageSource, data }) {
1797
- const gateEnabled = Boolean(accessGate);
1798
- const [authLoading, setAuthLoading] = (0, react.useState)(gateEnabled);
1799
- const [notAllowedError, setNotAllowedError] = (0, react.useState)(false);
1800
- const [authVerified, setAuthVerified] = (0, react.useState)(!gateEnabled || Boolean(authController.loginSkipped));
1801
- const canAccessMainView = authVerified && (!gateEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) && !notAllowedError;
1802
- (0, react.useEffect)(() => {
1803
- if (authController.loginSkipped) setAuthVerified(true);
1804
- }, [authController.loginSkipped]);
1805
- /**
1806
- * We use this ref to check the authentication only if the user has
1807
- * changed.
1808
- */
1809
- const checkedUserRef = (0, react.useRef)(void 0);
1810
- const checkAccess = (0, react.useCallback)(async () => {
1811
- if (disabled) return;
1812
- if (authController.initialLoading) return;
1813
- if (!authController.user && !authController.loginSkipped) {
1814
- checkedUserRef.current = void 0;
1815
- setAuthLoading(false);
1816
- setAuthVerified(false);
1817
- return;
1818
- }
1819
- const delegateUser = authController.user;
1820
- if (accessGate instanceof Function && delegateUser && !(0, fast_equals.deepEqual)(checkedUserRef.current?.uid, delegateUser.uid)) {
1821
- setAuthLoading(true);
1822
- try {
1823
- if (!await accessGate({
1824
- user: delegateUser,
1825
- authController,
1826
- data,
1827
- storageSource
1828
- })) {
1829
- authController.signOut();
1830
- setNotAllowedError(true);
1831
- }
1832
- } catch (e) {
1833
- setNotAllowedError(e);
1834
- authController.signOut();
1835
- }
1836
- setAuthLoading(false);
1837
- setAuthVerified(true);
1838
- checkedUserRef.current = delegateUser;
1839
- } else setAuthLoading(false);
1840
- if (!authController.initialLoading && !delegateUser) setAuthVerified(true);
1841
- }, [
1842
- disabled,
1843
- authController,
1844
- accessGate,
1845
- data,
1846
- storageSource
1847
- ]);
1848
- (0, react.useEffect)(() => {
1849
- checkAccess();
1850
- }, [checkAccess]);
1851
- return (0, react.useMemo)(() => ({
1852
- canAccessMainView,
1853
- authLoading: gateEnabled && authLoading,
1854
- notAllowedError,
1855
- authVerified
1856
- }), [
1857
- canAccessMainView,
1858
- gateEnabled,
1859
- authLoading,
1860
- notAllowedError,
1861
- authVerified
1862
- ]);
1863
- }
1864
- //#endregion
1865
- //#region src/components/social_icons.tsx
1866
- var googleIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1867
- xmlns: "http://www.w3.org/2000/svg",
1868
- viewBox: "0 0 64 64",
1869
- width: 24,
1870
- height: 24,
1871
- children: [
1872
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
1873
- id: "95yY7w43Oj6n2vH63j6HJb",
1874
- x1: "29.401",
1875
- x2: "29.401",
1876
- y1: "4.064",
1877
- y2: "106.734",
1878
- gradientTransform: "matrix(1 0 0 -1 0 66)",
1879
- gradientUnits: "userSpaceOnUse",
1880
- children: [
1881
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1882
- offset: "0",
1883
- stopColor: "#ff5840"
1884
- }),
1885
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1886
- offset: ".007",
1887
- stopColor: "#ff5840"
1888
- }),
1889
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1890
- offset: ".989",
1891
- stopColor: "#fa528c"
1892
- }),
1893
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1894
- offset: "1",
1895
- stopColor: "#fa528c"
1896
- })
1897
- ]
1898
- }),
1899
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1900
- fill: "url(#95yY7w43Oj6n2vH63j6HJb)",
1901
- d: "M47.46,15.5l-1.37,1.48c-1.34,1.44-3.5,1.67-5.15,0.6c-2.71-1.75-6.43-3.13-11-2.37 c-4.94,0.83-9.17,3.85-11.64, 7.97l-8.03-6.08C14.99,9.82,23.2,5,32.5,5c5,0,9.94,1.56,14.27,4.46 C48.81,10.83,49.13,13.71,47.46,15.5z"
1902
- }),
1903
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
1904
- id: "95yY7w43Oj6n2vH63j6HJc",
1905
- x1: "12.148",
1906
- x2: "12.148",
1907
- y1: ".872",
1908
- y2: "47.812",
1909
- gradientTransform: "matrix(1 0 0 -1 0 66)",
1910
- gradientUnits: "userSpaceOnUse",
1911
- children: [
1912
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1913
- offset: "0",
1914
- stopColor: "#feaa53"
1915
- }),
1916
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1917
- offset: ".612",
1918
- stopColor: "#ffcd49"
1919
- }),
1920
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1921
- offset: "1",
1922
- stopColor: "#ffde44"
1923
- })
1924
- ]
1925
- }),
1926
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1927
- fill: "url(#95yY7w43Oj6n2vH63j6HJc)",
1928
- d: "M16.01,30.91c-0.09,2.47,0.37,4.83,1.27,6.96l-8.21,6.05c-1.35-2.51-2.3-5.28-2.75-8.22 c-1.06-6.88,0.54-13.38, 3.95-18.6l8.03,6.08C16.93,25.47,16.1,28.11,16.01,30.91z"
1929
- }),
1930
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
1931
- id: "95yY7w43Oj6n2vH63j6HJd",
1932
- x1: "29.76",
1933
- x2: "29.76",
1934
- y1: "32.149",
1935
- y2: "-6.939",
1936
- gradientTransform: "matrix(1 0 0 -1 0 66)",
1937
- gradientUnits: "userSpaceOnUse",
1938
- children: [
1939
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1940
- offset: "0",
1941
- stopColor: "#42d778"
1942
- }),
1943
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1944
- offset: ".428",
1945
- stopColor: "#3dca76"
1946
- }),
1947
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1948
- offset: "1",
1949
- stopColor: "#34b171"
1950
- })
1951
- ]
1952
- }),
1953
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1954
- fill: "url(#95yY7w43Oj6n2vH63j6HJd)",
1955
- d: "M50.45,51.28c-4.55,4.07-10.61,6.57-17.36,6.71C22.91,58.2,13.66,52.53,9.07,43.92l8.21-6.05 C19.78,43.81, 25.67,48,32.5,48c3.94,0,7.52-1.28,10.33-3.44L50.45,51.28z"
1956
- }),
1957
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
1958
- id: "95yY7w43Oj6n2vH63j6HJe",
1959
- x1: "46",
1960
- x2: "46",
1961
- y1: "3.638",
1962
- y2: "35.593",
1963
- gradientTransform: "matrix(1 0 0 -1 0 66)",
1964
- gradientUnits: "userSpaceOnUse",
1965
- children: [
1966
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1967
- offset: "0",
1968
- stopColor: "#155cde"
1969
- }),
1970
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1971
- offset: ".278",
1972
- stopColor: "#1f7fe5"
1973
- }),
1974
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1975
- offset: ".569",
1976
- stopColor: "#279ceb"
1977
- }),
1978
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1979
- offset: ".82",
1980
- stopColor: "#2cafef"
1981
- }),
1982
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
1983
- offset: "1",
1984
- stopColor: "#2eb5f0"
1985
- })
1986
- ]
1987
- }),
1988
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1989
- fill: "url(#95yY7w43Oj6n2vH63j6HJe)",
1990
- d: "M59,31.97c0.01,7.73-3.26,14.58-8.55,19.31l-7.62-6.72c2.1-1.61,3.77-3.71,4.84-6.15\n c0.29-0.66-0.2-1.41-0.92-1.41H37c-2.21,0-4-1.79-4-4v-2c0-2.21,1.79-4,4-4h17C56.75,27,59,29.22,59,31.97z"
1991
- })
1992
- ]
1993
- }) });
1994
- var appleIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1995
- width: 24,
1996
- height: 24,
1997
- viewBox: "0 0 56 56",
1998
- style: { transform: "scale(2.8)" },
1999
- version: "1.1",
2000
- xmlns: "http://www.w3.org/2000/svg",
2001
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
2002
- stroke: mode === "light" ? "#424245" : "white",
2003
- strokeWidth: "0.5",
2004
- fillRule: "evenodd",
2005
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2006
- d: "M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z",
2007
- fill: mode === "light" ? "#424245" : "white",
2008
- fillRule: "nonzero"
2009
- })
2010
- })
2011
- });
2012
- var githubIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2013
- fill: mode === "light" ? "#1c1e21" : "white",
2014
- role: "img",
2015
- viewBox: "0 0 24 24",
2016
- width: 24,
2017
- height: 24,
2018
- xmlns: "http://www.w3.org/2000/svg",
2019
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" })
2020
- });
2021
- var facebookIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2022
- xmlns: "http://www.w3.org/2000/svg",
2023
- width: 24,
2024
- height: 24,
2025
- viewBox: "0 0 90 90",
2026
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2027
- d: "M90,15.001C90,7.119,82.884,0,75,0H15C7.116,0,0,7.119,0,15.001v59.998 C0,82.881,7.116,90,15.001,90H45V56H34V41h11v-5.844C45,25.077,52.568,16,61.875,16H74v15H61.875C60.548,31,59,32.611,59,35.024V41 h15v15H59v34h16c7.884,0,15-7.119,15-15.001V15.001z",
2028
- fill: mode === "light" ? "#39569c" : "white"
2029
- }) })
2030
- });
2031
- var microsoftIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2032
- xmlns: "http://www.w3.org/2000/svg",
2033
- width: 24,
2034
- height: 24,
2035
- viewBox: "0 0 480 480",
2036
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2037
- d: "M0.176,224L0.001,67.963l192-26.072V224H0.176z M224.001,37.241L479.937,0v224H224.001V37.241z M479.999,256l-0.062,224 l-255.936-36.008V256H479.999z M192.001,439.918L0.157,413.621L0.147,256h191.854V439.918z",
2038
- fill: mode === "light" ? "#00a2ed" : "white"
2039
- }) })
2040
- });
2041
- var twitterIcon = (mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2042
- xmlns: "http://www.w3.org/2000/svg",
2043
- width: 24,
2044
- height: 24,
2045
- viewBox: "0 0 24 24",
2046
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2047
- fill: mode === "light" ? "#00acee" : "white",
2048
- d: "M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"
2049
- })
2050
- });
2051
- //#endregion
2052
- //#region src/components/FirebaseLoginView.tsx
2053
- /**
2054
- * Use this component to render a login view, that updates
2055
- * the state of the {@link FirebaseAuthController} based on the result
2056
-
2057
- * @category Firebase
2058
- */
2059
- function FirebaseLoginView({ children, allowSkipLogin, logo, signInOptions, firebaseApp, authController, noUserComponent, disableSignupScreen = false, disableResetPassword = false, disabled = false, additionalComponent, notAllowedError, className }) {
2060
- const modeState = (0, _rebasepro_app.useModeController)();
2061
- const [passwordLoginSelected, setPasswordLoginSelected] = (0, react.useState)(false);
2062
- const [phoneLoginSelected, setPhoneLoginSelected] = (0, react.useState)(false);
2063
- const [fadeIn, setFadeIn] = (0, react.useState)(false);
2064
- (0, react.useEffect)(() => {
2065
- const timer = setTimeout(() => {
2066
- setFadeIn(true);
2067
- }, 50);
2068
- return () => clearTimeout(timer);
2069
- }, []);
2070
- const resolvedSignInOptions = signInOptions.map((o) => {
2071
- if (typeof o === "object") return o.provider;
2072
- else return o;
2073
- });
2074
- const sendMFASms = (0, react.useCallback)(() => {
2075
- const auth = (0, _firebase_auth.getAuth)(firebaseApp);
2076
- const recaptchaVerifier = new _firebase_auth.RecaptchaVerifier(auth, "recaptcha", { size: "invisible" });
2077
- const resolver = (0, _firebase_auth.getMultiFactorResolver)(auth, authController.authProviderError);
2078
- if (resolver.hints[0].factorId === _firebase_auth.PhoneMultiFactorGenerator.FACTOR_ID) {
2079
- const phoneInfoOptions = {
2080
- multiFactorHint: resolver.hints[0],
2081
- session: resolver.session
2082
- };
2083
- new _firebase_auth.PhoneAuthProvider(auth).verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier).then(function(verificationId) {
2084
- const verificationCode = String(window.prompt("Please enter the verification code that was sent to your mobile device."));
2085
- const cred = _firebase_auth.PhoneAuthProvider.credential(verificationId, verificationCode);
2086
- const multiFactorAssertion = _firebase_auth.PhoneMultiFactorGenerator.assertion(cred);
2087
- return resolver.resolveSignIn(multiFactorAssertion);
2088
- });
2089
- } else console.warn("Unsupported second factor.");
2090
- }, [authController.authProviderError]);
2091
- function buildErrorView() {
2092
- let errorView;
2093
- if (authController.user != null) return errorView;
2094
- const ignoredCodes = ["auth/popup-closed-by-user", "auth/cancelled-popup-request"];
2095
- if (authController.authProviderError) {
2096
- const authError = authController.authProviderError;
2097
- if (authError.code === "auth/operation-not-allowed" || authError.code === "auth/configuration-not-found") errorView = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2098
- className: "p-4",
2099
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, {
2100
- title: "Firebase Auth not enabled",
2101
- error: "You need to enable Firebase Auth and the corresponding login provider in your Firebase project"
2102
- })
2103
- }), firebaseApp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2104
- className: "p-4",
2105
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2106
- href: `https://console.firebase.google.com/project/${firebaseApp.options.projectId}/authentication/providers`,
2107
- rel: "noopener noreferrer",
2108
- target: "_blank",
2109
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2110
- variant: "text",
2111
- color: "error",
2112
- children: "Open Firebase configuration"
2113
- })
2114
- })
2115
- })] });
2116
- else if (authError.code === "auth/invalid-api-key") errorView = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2117
- className: "p-4",
2118
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, {
2119
- title: "Invalid API key",
2120
- error: "auth/invalid-api-key: Check that your Firebase config is set correctly in your `firebase_config.ts` file"
2121
- })
2122
- });
2123
- else if (authError.code === "auth/email-already-in-use") errorView = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2124
- className: "p-4",
2125
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, {
2126
- title: "Email already in use",
2127
- error: "The selected email is already in use by another account"
2128
- })
2129
- });
2130
- else if (authError.code === "auth/invalid-credential") errorView = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2131
- className: "p-4",
2132
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, {
2133
- title: "Invalid credential",
2134
- error: "The provided credential is not correct"
2135
- })
2136
- });
2137
- else if (!ignoredCodes.includes(authError.code)) {
2138
- if (authError.code === "auth/multi-factor-auth-required") sendMFASms();
2139
- errorView = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2140
- className: "p-4",
2141
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, { error: authController.authProviderError })
2142
- });
2143
- }
2144
- }
2145
- return errorView;
2146
- }
2147
- let logoComponent;
2148
- if (logo) logoComponent = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
2149
- src: logo,
2150
- style: {
2151
- height: "100%",
2152
- width: "100%",
2153
- objectFit: "contain"
2154
- },
2155
- alt: "Logo"
2156
- });
2157
- else logoComponent = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.RebaseLogo, {});
2158
- let notAllowedMessage;
2159
- if (notAllowedError) if (typeof notAllowedError === "string") notAllowedMessage = notAllowedError;
2160
- else if (notAllowedError instanceof Error) notAllowedMessage = notAllowedError.message;
2161
- else notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified access gate configuration";
2162
- const fadeStyle = {
2163
- opacity: fadeIn ? 1 : 0,
2164
- transition: "opacity 0.6s ease-in-out"
2165
- };
2166
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2167
- className: (0, _rebasepro_ui.cls)("flex flex-col items-center justify-center min-w-full p-4", className),
2168
- style: fadeStyle,
2169
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { id: "recaptcha" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2170
- className: "flex flex-col items-center w-full max-w-[500px]",
2171
- children: [
2172
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2173
- className: "p-1 w-64 h-64 m-4",
2174
- children: logoComponent
2175
- }),
2176
- children,
2177
- notAllowedMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2178
- className: "p-8",
2179
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, { error: notAllowedMessage })
2180
- }),
2181
- buildErrorView(),
2182
- !passwordLoginSelected && !phoneLoginSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2183
- className: "my-4 w-full",
2184
- children: [
2185
- buildOauthLoginButtons(authController, resolvedSignInOptions, modeState.mode, disabled),
2186
- resolvedSignInOptions.includes("password") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2187
- disabled,
2188
- text: "Email/password",
2189
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.MailIcon, { size: _rebasepro_ui.iconSize.medium }),
2190
- onClick: () => setPasswordLoginSelected(true)
2191
- }),
2192
- resolvedSignInOptions.includes("phone") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2193
- disabled,
2194
- text: "PhoneIcon number",
2195
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.PhoneIcon, { size: _rebasepro_ui.iconSize.medium }),
2196
- onClick: () => setPhoneLoginSelected(true)
2197
- }),
2198
- resolvedSignInOptions.includes("anonymous") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2199
- disabled,
2200
- text: "Log in anonymously",
2201
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.UserIcon, { size: "medium" }),
2202
- onClick: authController.anonymousLogin
2203
- }),
2204
- allowSkipLogin && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2205
- className: "m-1 mb-4",
2206
- variant: "text",
2207
- disabled,
2208
- onClick: authController.skipLogin,
2209
- children: "Skip login"
2210
- })
2211
- ]
2212
- }),
2213
- passwordLoginSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginForm, {
2214
- authController,
2215
- onClose: () => setPasswordLoginSelected(false),
2216
- mode: modeState.mode,
2217
- noUserComponent,
2218
- disableSignupScreen,
2219
- disableResetPassword
2220
- }),
2221
- phoneLoginSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhoneLoginForm, {
2222
- authController,
2223
- onClose: () => setPhoneLoginSelected(false)
2224
- }),
2225
- !passwordLoginSelected && !phoneLoginSelected && additionalComponent
2226
- ]
2227
- })]
2228
- });
2229
- }
2230
- function LoginButton({ icon, onClick, text, disabled }) {
2231
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2232
- className: "my-1 w-full",
2233
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2234
- className: (0, _rebasepro_ui.cls)("w-full bg-white dark:bg-surface-950 text-surface-900 dark:text-surface-100", disabled ? "" : "hover:text-surface-950 hover:dark:text-white"),
2235
- style: {
2236
- height: "40px",
2237
- borderRadius: "4px",
2238
- fontSize: "14px"
2239
- },
2240
- disabled,
2241
- onClick,
2242
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2243
- className: "p-1 flex h-8 items-center justify-items-center",
2244
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2245
- className: "flex flex-col w-8 items-center justify-items-center mr-4",
2246
- children: icon
2247
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2248
- className: "grow pl-2 text-center",
2249
- children: text
2250
- })]
2251
- })
2252
- })
2253
- });
2254
- }
2255
- function PhoneLoginForm({ onClose, authController }) {
2256
- useRecaptcha();
2257
- const [phone, setPhone] = (0, react.useState)();
2258
- const [code, setCode] = (0, react.useState)();
2259
- const [isInvalidCode, setIsInvalidCode] = (0, react.useState)(false);
2260
- const handleSubmit = async (event) => {
2261
- event.preventDefault();
2262
- if (code && authController.confirmationResult) {
2263
- setIsInvalidCode(false);
2264
- authController.confirmationResult.confirm(code).catch((e) => {
2265
- if (e.code === "auth/invalid-verification-code") setIsInvalidCode(true);
2266
- });
2267
- } else if (phone) authController.phoneLogin(phone, window.recaptchaVerifier);
2268
- };
2269
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2270
- onSubmit: handleSubmit,
2271
- children: [
2272
- isInvalidCode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2273
- className: "p-8",
2274
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ErrorView, { error: "Invalid confirmation code" })
2275
- }),
2276
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { id: RECAPTCHA_CONTAINER_ID }),
2277
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2278
- className: "flex flex-col gap-1",
2279
- children: [
2280
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
2281
- onClick: onClose,
2282
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.ArrowLeftIcon, { className: "w-5 h-5" })
2283
- }),
2284
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2285
- className: "p-1 flex",
2286
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
2287
- align: "center",
2288
- variant: "subtitle2",
2289
- children: "Please enter your phone number"
2290
- })
2291
- }),
2292
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
2293
- placeholder: "",
2294
- value: phone ?? "",
2295
- disabled: Boolean(phone && (authController.authLoading || authController.confirmationResult)),
2296
- type: "phone",
2297
- onChange: (event) => setPhone(event.target.value)
2298
- }),
2299
- Boolean(phone && authController.confirmationResult) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2300
- className: "mt-2 p-1 flex",
2301
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
2302
- align: "center",
2303
- variant: "subtitle2",
2304
- children: "Please enter the confirmation code"
2305
- })
2306
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
2307
- placeholder: "",
2308
- value: code ?? "",
2309
- type: "text",
2310
- onChange: (event) => setCode(event.target.value)
2311
- })] }),
2312
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2313
- className: "flex justify-end items-center w-full",
2314
- children: [authController.authLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, {
2315
- className: "p-1",
2316
- size: "small"
2317
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2318
- type: "submit",
2319
- children: "Ok"
2320
- })]
2321
- })
2322
- ]
2323
- })
2324
- ]
2325
- });
2326
- }
2327
- function LoginForm({ onClose, authController, mode, noUserComponent, disableSignupScreen, disableResetPassword }) {
2328
- const passwordRef = (0, react.useRef)(null);
2329
- const [loginState, setLoginState] = (0, react.useState)("email");
2330
- const [email, setEmail] = (0, react.useState)();
2331
- const [password, setPassword] = (0, react.useState)();
2332
- const [previouslyUsedMethodsForUser, setPreviouslyUsedMethodsForUser] = (0, react.useState)();
2333
- const [resettingPassword, setResettingPassword] = (0, react.useState)(false);
2334
- const snackbarController = (0, _rebasepro_app.useSnackbarController)();
2335
- (0, react.useEffect)(() => {
2336
- if ((loginState === "password" || loginState === "registration") && passwordRef.current) passwordRef.current.focus();
2337
- }, [loginState]);
2338
- (0, react.useEffect)(() => {
2339
- if (!document) return;
2340
- const escFunction = (event) => {
2341
- if (event.key === "Escape") onClose();
2342
- };
2343
- document.addEventListener("keydown", escFunction, false);
2344
- return () => {
2345
- document.removeEventListener("keydown", escFunction, false);
2346
- };
2347
- }, [onClose]);
2348
- function handleEnterEmail() {
2349
- if (email) {
2350
- authController.fetchSignInMethodsForEmail(email).then((availableProviders) => {
2351
- setPreviouslyUsedMethodsForUser(availableProviders.filter((p) => p !== "password"));
2352
- });
2353
- setLoginState("password");
2354
- }
2355
- }
2356
- function handleEnterPassword() {
2357
- if (email && password) authController.emailPasswordLogin(email, password);
2358
- }
2359
- function handleRegistration() {
2360
- if (email && password) authController.createUserWithEmailAndPassword(email, password);
2361
- }
2362
- const onBackPressed = () => {
2363
- if (loginState === "email") onClose();
2364
- else if (loginState === "password" || loginState === "registration") setLoginState("email");
2365
- else setPreviouslyUsedMethodsForUser(void 0);
2366
- };
2367
- const handleSubmit = (event) => {
2368
- event.preventDefault();
2369
- if (loginState === "email") handleEnterEmail();
2370
- else if (loginState === "password") handleEnterPassword();
2371
- else if (loginState === "registration") handleRegistration();
2372
- };
2373
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("form", {
2374
- className: "w-full",
2375
- onSubmit: handleSubmit,
2376
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2377
- className: "max-w-[480px] w-full flex flex-col gap-4",
2378
- children: [
2379
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
2380
- onClick: onBackPressed,
2381
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.ArrowLeftIcon, { className: "w-5 h-5" })
2382
- }),
2383
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: loginState === "registration" && noUserComponent }),
2384
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
2385
- className: `${loginState === "registration" && disableSignupScreen ? "hidden" : "flex"}`,
2386
- variant: "subtitle2",
2387
- children: loginState === "registration" ? "Please enter your email and password to create an account" : loginState === "password" ? "Please enter your password" : "Please enter your email"
2388
- }),
2389
- (loginState === "email" || loginState === "registration") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
2390
- placeholder: "Email",
2391
- autoFocus: true,
2392
- value: email ?? "",
2393
- disabled: authController.authLoading,
2394
- type: "email",
2395
- onChange: (event) => setEmail(event.target.value)
2396
- }),
2397
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2398
- className: `${loginState === "password" || loginState === "registration" && !disableSignupScreen ? "block" : "hidden"}`,
2399
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
2400
- placeholder: "Password",
2401
- value: password ?? "",
2402
- disabled: authController.authLoading,
2403
- inputRef: passwordRef,
2404
- type: "password",
2405
- onChange: (event) => setPassword(event.target.value)
2406
- })
2407
- }),
2408
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2409
- className: `${loginState === "registration" && disableSignupScreen ? "hidden" : "flex"} justify-end items-center w-full flex gap-2`,
2410
- children: [
2411
- authController.authLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, {
2412
- className: "p-1",
2413
- size: "small"
2414
- }),
2415
- !disableResetPassword && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.LoadingButton, {
2416
- variant: "text",
2417
- loading: resettingPassword,
2418
- onClick: email ? async () => {
2419
- setResettingPassword(true);
2420
- try {
2421
- try {
2422
- await authController.sendPasswordResetEmail(email);
2423
- snackbarController.open({
2424
- message: "Password reset email sent",
2425
- type: "success"
2426
- });
2427
- } catch (e) {
2428
- snackbarController.open({
2429
- message: e instanceof Error ? e.message : String(e),
2430
- type: "error"
2431
- });
2432
- }
2433
- } finally {
2434
- setResettingPassword(false);
2435
- }
2436
- } : void 0,
2437
- children: "Reset password"
2438
- }),
2439
- !disableSignupScreen && loginState === "email" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2440
- variant: "text",
2441
- onClick: () => setLoginState("registration"),
2442
- children: "New user"
2443
- }),
2444
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
2445
- type: "submit",
2446
- children: loginState === "registration" ? "Create account" : loginState === "password" ? "Login" : "Login"
2447
- })
2448
- ]
2449
- }),
2450
- previouslyUsedMethodsForUser && previouslyUsedMethodsForUser.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2451
- className: "flex flex-col gap-4 p-4",
2452
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
2453
- variant: "subtitle2",
2454
- children: "You already have an account"
2455
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
2456
- variant: "body2",
2457
- children: ["You can use one of these methods to login with ", email]
2458
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: previouslyUsedMethodsForUser && buildOauthLoginButtons(authController, previouslyUsedMethodsForUser, mode, false) })]
2459
- })
2460
- ]
2461
- })
2462
- });
2463
- }
2464
- function buildOauthLoginButtons(authController, providers, mode, disabled) {
2465
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2466
- providers.includes("google.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2467
- disabled,
2468
- text: "Sign in with Google",
2469
- icon: googleIcon(mode),
2470
- onClick: authController.googleLogin
2471
- }),
2472
- providers.includes("microsoft.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2473
- disabled,
2474
- text: "Sign in with Microsoft",
2475
- icon: microsoftIcon(mode),
2476
- onClick: authController.microsoftLogin
2477
- }),
2478
- providers.includes("apple.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2479
- disabled,
2480
- text: "Sign in with Apple",
2481
- icon: appleIcon(mode),
2482
- onClick: authController.appleLogin
2483
- }),
2484
- providers.includes("github.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2485
- disabled,
2486
- text: "Sign in with Github",
2487
- icon: githubIcon(mode),
2488
- onClick: authController.githubLogin
2489
- }),
2490
- providers.includes("facebook.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2491
- disabled,
2492
- text: "Sign in with Facebook",
2493
- icon: facebookIcon(mode),
2494
- onClick: authController.facebookLogin
2495
- }),
2496
- providers.includes("twitter.com") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginButton, {
2497
- disabled,
2498
- text: "Sign in with Twitter",
2499
- icon: twitterIcon(mode),
2500
- onClick: authController.twitterLogin
2501
- })
2502
- ] });
2503
- }
2504
- //#endregion
2505
- //#region src/components/RebaseFirebaseApp.tsx
2506
- var DEFAULT_SIGN_IN_OPTIONS = [_firebase_auth.GoogleAuthProvider.PROVIDER_ID];
2507
- /**
2508
- * This is the default implementation of a Rebase app using the Firebase services
2509
- * as a backend.
2510
- * You can use this component as a full app, by specifying collections and
2511
- * entity collections.
2512
- *
2513
- * This component is in charge of initialising Firebase, with the given
2514
- * configuration object.
2515
- *
2516
- * If you are building a larger app and need finer control, you can use
2517
- * {@link Rebase}, {@link Scaffold}, {@link SideDialogs}
2518
- * and {@link NavigationRoutes} instead.
2519
- *
2520
- * @param props
2521
-
2522
- * @category Firebase
2523
- */
2524
- function RebaseFirebaseApp({ name, logo, logoDark, accessGate, collections, views, adminViews, textSearchControllerBuilder, allowSkipLogin, signInOptions = DEFAULT_SIGN_IN_OPTIONS, firebaseConfig, onFirebaseInit, appCheckOptions, dateTimeFormat, locale, basePath, baseCollectionPath, onAnalyticsEvent, propertyConfigs: propertyConfigsProp, plugins, autoOpenDrawer, firestoreIndexesBuilder, components, localTextSearchEnabled = false }) {
2525
- /**
2526
- * Update the browser title and icon
2527
- */
2528
- (0, _rebasepro_app.useBrowserTitleAndIcon)(name, logo);
2529
- const propertyConfigs = (propertyConfigsProp ?? []).map((pc) => ({ [pc.key]: pc })).reduce((a, b) => ({
2530
- ...a,
2531
- ...b
2532
- }), {});
2533
- const { firebaseApp, firebaseConfigLoading, configError } = useInitialiseFirebase({
2534
- onFirebaseInit,
2535
- firebaseConfig
2536
- });
2537
- /**
2538
- * Controller used to manage the dark or light color mode
2539
- */
2540
- const modeController = (0, _rebasepro_app.useBuildModeController)();
2541
- const adminModeController = (0, _rebasepro_app.useBuildAdminModeController)();
2542
- const { loading, appCheckVerified, error } = useAppCheck({
2543
- firebaseApp,
2544
- options: appCheckOptions
2545
- });
2546
- /**
2547
- * Controller for managing authentication
2548
- */
2549
- const authController = useFirebaseAuthController({
2550
- firebaseApp,
2551
- signInOptions
2552
- });
2553
- /**
2554
- * Controller for saving some user preferences locally.
2555
- */
2556
- const userConfigPersistence = (0, _rebasepro_app.useBuildLocalConfigurationPersistence)();
2557
- const firestoreDelegate = useFirestoreDriver({
2558
- firebaseApp,
2559
- textSearchControllerBuilder,
2560
- firestoreIndexesBuilder,
2561
- localTextSearchEnabled
2562
- });
2563
- /**
2564
- * Controller used for saving and fetching files in storage
2565
- */
2566
- const storageSource = useFirebaseStorageSource({ firebaseApp });
2567
- /**
2568
- * Validate access gate
2569
- */
2570
- const { authLoading, canAccessMainView, notAllowedError } = useFirebaseAccessGate({
2571
- authController,
2572
- accessGate,
2573
- data: (0, _rebasepro_common.buildRebaseData)(firestoreDelegate),
2574
- storageSource
2575
- });
2576
- const collectionRegistryController = (0, _rebasepro_admin.useBuildCollectionRegistryController)({ userConfigPersistence });
2577
- const urlController = (0, _rebasepro_admin.useBuildUrlController)({
2578
- basePath: basePath ?? "/",
2579
- baseCollectionPath: baseCollectionPath ?? "/c",
2580
- collectionRegistryController
2581
- });
2582
- useBuildUserManagement({
2583
- authController,
2584
- dataSourceDelegate: firestoreDelegate
2585
- });
2586
- const navigationStateController = (0, _rebasepro_admin.useBuildNavigationStateController)({
2587
- collections,
2588
- views,
2589
- adminViews,
2590
- authController,
2591
- data: (0, _rebasepro_common.buildRebaseData)(firestoreDelegate),
2592
- plugins,
2593
- collectionRegistryController,
2594
- urlController,
2595
- adminMode: adminModeController.mode
2596
- });
2597
- if (firebaseConfigLoading || !firebaseApp || loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgressCenter, {}) });
2598
- if (configError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CenteredView, { children: configError });
2599
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.SnackbarProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.ModeControllerProvider, {
2600
- value: modeController,
2601
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.AdminModeControllerProvider, {
2602
- value: adminModeController,
2603
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.CollectionRegistryContext.Provider, {
2604
- value: collectionRegistryController,
2605
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.UrlContext.Provider, {
2606
- value: urlController,
2607
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.NavigationStateContext.Provider, {
2608
- value: navigationStateController,
2609
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.Rebase, {
2610
- authController,
2611
- userConfigPersistence,
2612
- dateTimeFormat,
2613
- dataSources: [{
2614
- key: _rebasepro_types.DEFAULT_DATA_SOURCE_KEY,
2615
- engine: "firestore",
2616
- transport: "direct",
2617
- driver: firestoreDelegate
2618
- }],
2619
- storageSource,
2620
- entityLinkBuilder: ({ entity }) => `https://console.firebase.google.com/project/${firebaseApp.options.projectId}/firestore/data/${entity.path}/${entity.id}`,
2621
- locale,
2622
- onAnalyticsEvent,
2623
- plugins,
2624
- propertyConfigs,
2625
- children: ({ context, loading }) => {
2626
- let component;
2627
- if (loading || authLoading) component = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgressCenter, { size: "large" });
2628
- else {
2629
- const usedLogo = modeController.mode === "dark" && logoDark ? logoDark : logo;
2630
- if (!canAccessMainView) component = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(components?.LoginView ?? FirebaseLoginView, {
2631
- logo: usedLogo,
2632
- allowSkipLogin,
2633
- signInOptions: signInOptions ?? DEFAULT_SIGN_IN_OPTIONS,
2634
- firebaseApp,
2635
- authController,
2636
- notAllowedError: notAllowedError instanceof Error ? notAllowedError : typeof notAllowedError === "string" ? notAllowedError : void 0
2637
- });
2638
- else {
2639
- const firstCollectionEntry = navigationStateController.topLevelNavigation?.navigationEntries.find((e) => e.type === "collection");
2640
- const fallbackRoute = firstCollectionEntry ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Navigate, {
2641
- to: urlController.buildUrlCollectionPath(firstCollectionEntry.id),
2642
- replace: true
2643
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CenteredView, { children: "No home page or collections provided." });
2644
- component = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.SidePanelProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_app.RebaseRoutes, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_router_dom.Route, {
2645
- element: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_admin.Scaffold, {
2646
- logo: usedLogo,
2647
- autoOpenDrawer,
2648
- children: [
2649
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.AppBar, {
2650
- title: name,
2651
- logo: usedLogo
2652
- }),
2653
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.Drawer, {}),
2654
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Outlet, {}),
2655
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.SideDialogs, {})
2656
- ]
2657
- }),
2658
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
2659
- path: "/",
2660
- element: components?.HomePage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(components.HomePage, {}) : fallbackRoute
2661
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
2662
- path: "/c/*",
2663
- element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_admin.RebaseRoute, {})
2664
- })]
2665
- }) }) });
2666
- }
2667
- }
2668
- return component;
2669
- }
2670
- })
2671
- })
2672
- })
2673
- })
2674
- })
2675
- }) });
2676
- }
2677
- //#endregion
2678
- exports.FirebaseLoginView = FirebaseLoginView;
2679
- exports.LoginButton = LoginButton;
2680
- exports.RECAPTCHA_CONTAINER_ID = RECAPTCHA_CONTAINER_ID;
2681
- exports.RebaseFirebaseApp = RebaseFirebaseApp;
2682
- exports.buildCollectionId = buildCollectionId;
2683
- exports.buildExternalSearchController = buildExternalSearchController;
2684
- exports.buildPineconeSearchController = buildPineconeSearchController;
2685
- exports.buildRebaseSearchController = buildRebaseSearchController;
2686
- exports.cmsToFirestoreModel = cmsToFirestoreModel;
2687
- exports.docToCollection = docToCollection;
2688
- exports.docsToCollectionTree = docsToCollectionTree;
2689
- exports.firestoreToCMSModel = firestoreToCMSModel;
2690
- exports.getFirestoreDataInPath = getFirestoreDataInPath;
2691
- exports.localSearchControllerBuilder = localSearchControllerBuilder;
2692
- exports.performAlgoliaTextSearch = performAlgoliaTextSearch;
2693
- exports.performPineconeTextSearch = performPineconeTextSearch;
2694
- exports.useAppCheck = useAppCheck;
2695
- exports.useBuildUserManagement = useBuildUserManagement;
2696
- exports.useFirebaseAccessGate = useFirebaseAccessGate;
2697
- exports.useFirebaseAuthController = useFirebaseAuthController;
2698
- exports.useFirebaseRTDBDelegate = useFirebaseRTDBDelegate;
2699
- exports.useFirebaseStorageSource = useFirebaseStorageSource;
2700
- exports.useFirestoreDriver = useFirestoreDriver;
2701
- exports.useInitialiseFirebase = useInitialiseFirebase;
2702
- exports.useRecaptcha = useRecaptcha;
2703
- });
2704
-
2705
- //# sourceMappingURL=index.umd.js.map