@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
|