@rebasepro/client-firebase 0.0.1-canary.4d4fb3e
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/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/components/FirebaseLoginView.d.ts +72 -0
- package/dist/components/RebaseFirebaseApp.d.ts +19 -0
- package/dist/components/RebaseFirebaseAppProps.d.ts +144 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/social_icons.d.ts +6 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/useAppCheck.d.ts +20 -0
- package/dist/hooks/useFirebaseAuthController.d.ts +15 -0
- package/dist/hooks/useFirebaseRealTimeDBDelegate.d.ts +5 -0
- package/dist/hooks/useFirebaseStorageSource.d.ts +14 -0
- package/dist/hooks/useFirestoreDriver.d.ts +56 -0
- package/dist/hooks/useInitialiseFirebase.d.ts +34 -0
- package/dist/hooks/useRecaptcha.d.ts +8 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +2757 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2743 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/social_icons.d.ts +6 -0
- package/dist/types/appcheck.d.ts +10 -0
- package/dist/types/auth.d.ts +41 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/text_search.d.ts +39 -0
- package/dist/utils/algolia.d.ts +9 -0
- package/dist/utils/collections_firestore.d.ts +5 -0
- package/dist/utils/database.d.ts +2 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/local_text_search_controller.d.ts +2 -0
- package/dist/utils/pinecone.d.ts +24 -0
- package/dist/utils/rebase_search_controller.d.ts +73 -0
- package/dist/utils/text_search_controller.d.ts +13 -0
- package/package.json +61 -0
- package/src/components/FirebaseLoginView.tsx +703 -0
- package/src/components/RebaseFirebaseApp.tsx +275 -0
- package/src/components/RebaseFirebaseAppProps.tsx +180 -0
- package/src/components/index.ts +3 -0
- package/src/components/social_icons.tsx +135 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useAppCheck.ts +101 -0
- package/src/hooks/useFirebaseAuthController.ts +334 -0
- package/src/hooks/useFirebaseRealTimeDBDelegate.ts +269 -0
- package/src/hooks/useFirebaseStorageSource.ts +208 -0
- package/src/hooks/useFirestoreDriver.ts +778 -0
- package/src/hooks/useInitialiseFirebase.ts +132 -0
- package/src/hooks/useRecaptcha.tsx +28 -0
- package/src/index.ts +4 -0
- package/src/social_icons.tsx +135 -0
- package/src/types/appcheck.ts +11 -0
- package/src/types/auth.tsx +74 -0
- package/src/types/index.ts +3 -0
- package/src/types/text_search.ts +42 -0
- package/src/utils/algolia.ts +27 -0
- package/src/utils/collections_firestore.ts +149 -0
- package/src/utils/database.ts +39 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/local_text_search_controller.ts +143 -0
- package/src/utils/pinecone.ts +75 -0
- package/src/utils/rebase_search_controller.ts +356 -0
- package/src/utils/text_search_controller.ts +34 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { deleteApp, FirebaseApp, getApps, initializeApp } from "@firebase/app";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @group Firebase
|
|
7
|
+
*/
|
|
8
|
+
export interface InitialiseFirebaseResult {
|
|
9
|
+
firebaseConfigLoading: boolean,
|
|
10
|
+
firebaseApp?: FirebaseApp;
|
|
11
|
+
configError?: string,
|
|
12
|
+
firebaseConfigError?: Error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const hostingError = "It seems like the provided Firebase config is not correct. If you \n" +
|
|
16
|
+
"are using the credentials provided automatically by Firebase \n" +
|
|
17
|
+
"Hosting, make sure you link your Firebase app to Firebase Hosting. \n";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Function used to initialise Firebase, either by using the provided config,
|
|
21
|
+
* or by fetching it by Firebase Hosting, if not specified.
|
|
22
|
+
*
|
|
23
|
+
* It works as a hook that gives you the loading state and the used
|
|
24
|
+
* configuration.
|
|
25
|
+
*
|
|
26
|
+
* You most likely only need to use this if you are developing a custom app. You can also not use this component
|
|
27
|
+
* and initialise Firebase yourself.
|
|
28
|
+
*
|
|
29
|
+
* @param onFirebaseInit
|
|
30
|
+
* @param firebaseConfig
|
|
31
|
+
* @param fromUrl
|
|
32
|
+
* @param name
|
|
33
|
+
* @param authDomain
|
|
34
|
+
* @group Firebase
|
|
35
|
+
*/
|
|
36
|
+
export function useInitialiseFirebase({
|
|
37
|
+
firebaseConfig,
|
|
38
|
+
fromUrl,
|
|
39
|
+
onFirebaseInit,
|
|
40
|
+
name,
|
|
41
|
+
authDomain
|
|
42
|
+
}: {
|
|
43
|
+
firebaseConfig?: Record<string, unknown>,
|
|
44
|
+
fromUrl?: string | undefined,
|
|
45
|
+
onFirebaseInit?: ((config: object, firebaseApp: FirebaseApp) => void) | undefined,
|
|
46
|
+
name?: string;
|
|
47
|
+
authDomain?: string;
|
|
48
|
+
}): InitialiseFirebaseResult {
|
|
49
|
+
|
|
50
|
+
const [firebaseApp, setFirebaseApp] = useState<FirebaseApp | undefined>();
|
|
51
|
+
const [firebaseConfigLoading, setFirebaseConfigLoading] = useState<boolean>(false);
|
|
52
|
+
const [configError, setConfigError] = useState<string>();
|
|
53
|
+
|
|
54
|
+
const initFirebase = useCallback((config: Record<string, unknown>) => {
|
|
55
|
+
|
|
56
|
+
if (config.projectId === firebaseApp?.options.projectId) {
|
|
57
|
+
console.debug("Firebase app already initialised with the same project ID. This should happen only in development mode.");
|
|
58
|
+
setConfigError(undefined);
|
|
59
|
+
setFirebaseConfigLoading(false);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const targetName = name ?? "[DEFAULT]";
|
|
65
|
+
const currentApps = getApps();
|
|
66
|
+
const existingApp = currentApps.find(app => app.name === targetName);
|
|
67
|
+
if (existingApp) {
|
|
68
|
+
deleteApp(existingApp);
|
|
69
|
+
}
|
|
70
|
+
const initialisedFirebaseApp = initializeApp(config, targetName);
|
|
71
|
+
setConfigError(undefined);
|
|
72
|
+
setFirebaseConfigLoading(false);
|
|
73
|
+
setFirebaseApp(initialisedFirebaseApp);
|
|
74
|
+
} catch (e: any) {
|
|
75
|
+
console.error("Error initialising Firebase", e);
|
|
76
|
+
setConfigError(hostingError + "\n" + (e.message ?? JSON.stringify(e)));
|
|
77
|
+
}
|
|
78
|
+
}, [name]);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (onFirebaseInit && firebaseConfig && firebaseApp) {
|
|
82
|
+
onFirebaseInit(firebaseConfig, firebaseApp);
|
|
83
|
+
}
|
|
84
|
+
}, [firebaseApp]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
|
|
88
|
+
setFirebaseConfigLoading(true);
|
|
89
|
+
|
|
90
|
+
function fetchFromUrl(url: string) {
|
|
91
|
+
fetch(url)
|
|
92
|
+
.then(async response => {
|
|
93
|
+
console.debug("Firebase init response", response.status);
|
|
94
|
+
if (response && response.status < 300) {
|
|
95
|
+
const config = await response.json();
|
|
96
|
+
if (authDomain) config.authDomain = authDomain;
|
|
97
|
+
initFirebase(config);
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
.catch(e => {
|
|
101
|
+
setFirebaseConfigLoading(false);
|
|
102
|
+
setConfigError(
|
|
103
|
+
"Could not load Firebase configuration from Firebase hosting. " +
|
|
104
|
+
"If the app is not deployed in Firebase hosting, you need to specify the configuration manually" +
|
|
105
|
+
e.toString()
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (firebaseConfig) {
|
|
112
|
+
initFirebase(firebaseConfig);
|
|
113
|
+
} else {
|
|
114
|
+
if (fromUrl) {
|
|
115
|
+
fetchFromUrl(fromUrl);
|
|
116
|
+
} else if (process.env.NODE_ENV === "production") {
|
|
117
|
+
fetchFromUrl("/__/firebase/init.json");
|
|
118
|
+
} else {
|
|
119
|
+
setFirebaseConfigLoading(false);
|
|
120
|
+
setConfigError(
|
|
121
|
+
"You need to deploy the app to Firebase hosting or specify a Firebase configuration object"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
firebaseApp,
|
|
129
|
+
firebaseConfigLoading,
|
|
130
|
+
configError
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { getAuth, RecaptchaVerifier } from "@firebase/auth";
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
recaptchaVerifier: RecaptchaVerifier;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const RECAPTCHA_CONTAINER_ID = "recaptcha-container" as const;
|
|
11
|
+
|
|
12
|
+
export function useRecaptcha() {
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!window || window?.recaptchaVerifier) return;
|
|
15
|
+
|
|
16
|
+
const auth = getAuth();
|
|
17
|
+
|
|
18
|
+
window.recaptchaVerifier = new RecaptchaVerifier(
|
|
19
|
+
auth,
|
|
20
|
+
RECAPTCHA_CONTAINER_ID,
|
|
21
|
+
{
|
|
22
|
+
size: "invisible"
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export const googleIcon = (mode: "light" | "dark") => <>
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
viewBox="0 0 64 64"
|
|
5
|
+
width={32}
|
|
6
|
+
height={32}
|
|
7
|
+
>
|
|
8
|
+
<linearGradient
|
|
9
|
+
id="95yY7w43Oj6n2vH63j6HJb"
|
|
10
|
+
x1="29.401"
|
|
11
|
+
x2="29.401"
|
|
12
|
+
y1="4.064"
|
|
13
|
+
y2="106.734"
|
|
14
|
+
gradientTransform="matrix(1 0 0 -1 0 66)"
|
|
15
|
+
gradientUnits="userSpaceOnUse"
|
|
16
|
+
>
|
|
17
|
+
<stop offset="0" stopColor="#ff5840"/>
|
|
18
|
+
<stop offset=".007" stopColor="#ff5840"/>
|
|
19
|
+
<stop offset=".989" stopColor="#fa528c"/>
|
|
20
|
+
<stop offset="1" stopColor="#fa528c"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
<path
|
|
23
|
+
fill="url(#95yY7w43Oj6n2vH63j6HJb)"
|
|
24
|
+
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"
|
|
25
|
+
/>
|
|
26
|
+
<linearGradient
|
|
27
|
+
id="95yY7w43Oj6n2vH63j6HJc"
|
|
28
|
+
x1="12.148"
|
|
29
|
+
x2="12.148"
|
|
30
|
+
y1=".872"
|
|
31
|
+
y2="47.812"
|
|
32
|
+
gradientTransform="matrix(1 0 0 -1 0 66)"
|
|
33
|
+
gradientUnits="userSpaceOnUse"
|
|
34
|
+
>
|
|
35
|
+
<stop offset="0" stopColor="#feaa53"/>
|
|
36
|
+
<stop offset=".612" stopColor="#ffcd49"/>
|
|
37
|
+
<stop offset="1" stopColor="#ffde44"/>
|
|
38
|
+
</linearGradient>
|
|
39
|
+
<path
|
|
40
|
+
fill="url(#95yY7w43Oj6n2vH63j6HJc)"
|
|
41
|
+
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"
|
|
42
|
+
/>
|
|
43
|
+
<linearGradient
|
|
44
|
+
id="95yY7w43Oj6n2vH63j6HJd"
|
|
45
|
+
x1="29.76"
|
|
46
|
+
x2="29.76"
|
|
47
|
+
y1="32.149"
|
|
48
|
+
y2="-6.939"
|
|
49
|
+
gradientTransform="matrix(1 0 0 -1 0 66)"
|
|
50
|
+
gradientUnits="userSpaceOnUse"
|
|
51
|
+
>
|
|
52
|
+
<stop offset="0" stopColor="#42d778"/>
|
|
53
|
+
<stop offset=".428" stopColor="#3dca76"/>
|
|
54
|
+
<stop offset="1" stopColor="#34b171"/>
|
|
55
|
+
</linearGradient>
|
|
56
|
+
<path
|
|
57
|
+
fill="url(#95yY7w43Oj6n2vH63j6HJd)"
|
|
58
|
+
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"
|
|
59
|
+
/>
|
|
60
|
+
<linearGradient
|
|
61
|
+
id="95yY7w43Oj6n2vH63j6HJe"
|
|
62
|
+
x1="46"
|
|
63
|
+
x2="46"
|
|
64
|
+
y1="3.638"
|
|
65
|
+
y2="35.593"
|
|
66
|
+
gradientTransform="matrix(1 0 0 -1 0 66)"
|
|
67
|
+
gradientUnits="userSpaceOnUse"
|
|
68
|
+
>
|
|
69
|
+
<stop offset="0" stopColor="#155cde"/>
|
|
70
|
+
<stop offset=".278" stopColor="#1f7fe5"/>
|
|
71
|
+
<stop offset=".569" stopColor="#279ceb"/>
|
|
72
|
+
<stop offset=".82" stopColor="#2cafef"/>
|
|
73
|
+
<stop offset="1" stopColor="#2eb5f0"/>
|
|
74
|
+
</linearGradient>
|
|
75
|
+
<path
|
|
76
|
+
fill="url(#95yY7w43Oj6n2vH63j6HJe)"
|
|
77
|
+
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
|
|
78
|
+
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"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
</>;
|
|
82
|
+
|
|
83
|
+
export const appleIcon = (mode: "light" | "dark") => <svg width={32} height={32}
|
|
84
|
+
viewBox="0 0 56 56"
|
|
85
|
+
style={{ transform: "scale(2.8)" }}
|
|
86
|
+
version="1.1"
|
|
87
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
88
|
+
<g stroke={mode === "light" ? "#424245" : "white"} strokeWidth="0.5"
|
|
89
|
+
fillRule="evenodd">
|
|
90
|
+
<path
|
|
91
|
+
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"
|
|
92
|
+
fill={mode === "light" ? "#424245" : "white"} fillRule="nonzero"/>
|
|
93
|
+
</g>
|
|
94
|
+
</svg>;
|
|
95
|
+
|
|
96
|
+
export const githubIcon = (mode: "light" | "dark") => <svg
|
|
97
|
+
fill={mode === "light" ? "#1c1e21" : "white"}
|
|
98
|
+
role="img"
|
|
99
|
+
viewBox="0 0 24 24"
|
|
100
|
+
width={28}
|
|
101
|
+
height={28}
|
|
102
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
103
|
+
<path
|
|
104
|
+
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"/>
|
|
105
|
+
</svg>;
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
export const facebookIcon = (mode: "light" | "dark") => <svg
|
|
109
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
110
|
+
width={28} height={28}
|
|
111
|
+
viewBox="0 0 90 90">
|
|
112
|
+
<g>
|
|
113
|
+
<path
|
|
114
|
+
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"
|
|
115
|
+
fill={mode === "light" ? "#39569c" : "white"}/>
|
|
116
|
+
</g>
|
|
117
|
+
</svg>;
|
|
118
|
+
|
|
119
|
+
export const microsoftIcon = (mode: "light" | "dark") => <svg
|
|
120
|
+
xmlns="http://www.w3.org/2000/svg" width={28} height={28}
|
|
121
|
+
viewBox="0 0 480 480">
|
|
122
|
+
<g>
|
|
123
|
+
<path
|
|
124
|
+
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"
|
|
125
|
+
fill={mode === "light" ? "#00a2ed" : "white"}/>
|
|
126
|
+
</g>
|
|
127
|
+
</svg>;
|
|
128
|
+
|
|
129
|
+
export const twitterIcon = (mode: "light" | "dark") => <svg
|
|
130
|
+
xmlns="http://www.w3.org/2000/svg" width={28} height={28}
|
|
131
|
+
viewBox="0 0 24 24">
|
|
132
|
+
<path fill={mode === "light" ? "#00acee" : "white"}
|
|
133
|
+
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"/>
|
|
134
|
+
</svg>;
|
|
135
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from "@firebase/app-check";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @group Firebase
|
|
5
|
+
*/
|
|
6
|
+
export interface AppCheckOptions {
|
|
7
|
+
provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
|
|
8
|
+
isTokenAutoRefreshEnabled?: boolean;
|
|
9
|
+
debugToken?: string;
|
|
10
|
+
forceRefresh?: boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ApplicationVerifier, ConfirmationResult, User as FirebaseUser } from "@firebase/auth";
|
|
2
|
+
|
|
3
|
+
import { AuthController, Role, User } from "@rebasepro/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @group Firebase
|
|
7
|
+
*/
|
|
8
|
+
export type FirebaseSignInProvider =
|
|
9
|
+
| "password"
|
|
10
|
+
| "phone"
|
|
11
|
+
| "anonymous"
|
|
12
|
+
| "google.com"
|
|
13
|
+
| "facebook.com"
|
|
14
|
+
| "github.com"
|
|
15
|
+
| "twitter.com"
|
|
16
|
+
| "microsoft.com"
|
|
17
|
+
| "apple.com";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @group Firebase
|
|
21
|
+
*/
|
|
22
|
+
export type FirebaseSignInOption = {
|
|
23
|
+
provider: FirebaseSignInProvider;
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
customParameters?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type FirebaseUserWrapper = User & FirebaseUser & {
|
|
29
|
+
firebaseUser: FirebaseUser | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @group Firebase
|
|
34
|
+
*/
|
|
35
|
+
export type FirebaseAuthController<USER extends User = FirebaseUserWrapper, ExtraData = any> =
|
|
36
|
+
AuthController<USER, ExtraData>
|
|
37
|
+
& {
|
|
38
|
+
|
|
39
|
+
confirmationResult?: ConfirmationResult;
|
|
40
|
+
|
|
41
|
+
googleLogin: () => void;
|
|
42
|
+
|
|
43
|
+
anonymousLogin: () => void;
|
|
44
|
+
|
|
45
|
+
appleLogin: () => void;
|
|
46
|
+
|
|
47
|
+
facebookLogin: () => void;
|
|
48
|
+
|
|
49
|
+
githubLogin: () => void;
|
|
50
|
+
|
|
51
|
+
microsoftLogin: () => void;
|
|
52
|
+
|
|
53
|
+
twitterLogin: () => void;
|
|
54
|
+
|
|
55
|
+
emailPasswordLogin: (email: string, password: string) => void;
|
|
56
|
+
|
|
57
|
+
fetchSignInMethodsForEmail: (email: string) => Promise<string[]>;
|
|
58
|
+
|
|
59
|
+
createUserWithEmailAndPassword: (email: string, password: string) => void;
|
|
60
|
+
|
|
61
|
+
sendPasswordResetEmail: (email: string) => Promise<void>;
|
|
62
|
+
|
|
63
|
+
phoneLogin: (phone: string, applicationVerifier: ApplicationVerifier) => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Skip login
|
|
67
|
+
*/
|
|
68
|
+
skipLogin: () => void;
|
|
69
|
+
|
|
70
|
+
setUser: (user: USER | null) => void;
|
|
71
|
+
|
|
72
|
+
setUserRoles: (roles: Role[]) => void;
|
|
73
|
+
|
|
74
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { User as FirebaseUser } from "@firebase/auth";
|
|
2
|
+
import { FirebaseApp } from "@firebase/app";
|
|
3
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
4
|
+
|
|
5
|
+
export type FirestoreTextSearchControllerBuilder = (props: {
|
|
6
|
+
firebaseApp: FirebaseApp;
|
|
7
|
+
}) => FirestoreTextSearchController;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Use this controller to return a list of ids from a search index, given a
|
|
11
|
+
* `path` and a `searchString`.
|
|
12
|
+
* Firestore does not support text search directly, so we need to rely on an external
|
|
13
|
+
* index, such as Algolia.
|
|
14
|
+
* Note that you will get text search requests for collections that have the
|
|
15
|
+
* `textSearchEnabled` flag set to `true`.
|
|
16
|
+
* @see performAlgoliaTextSearch
|
|
17
|
+
* @group Firebase
|
|
18
|
+
*/
|
|
19
|
+
export type FirestoreTextSearchController = {
|
|
20
|
+
/**
|
|
21
|
+
* This method is called when a search delegate is ready to be used.
|
|
22
|
+
* Return true if this path can be handled by this controller.
|
|
23
|
+
* @param props
|
|
24
|
+
*/
|
|
25
|
+
init: (props: {
|
|
26
|
+
path: string,
|
|
27
|
+
databaseId?: string,
|
|
28
|
+
collection?: EntityCollection
|
|
29
|
+
}) => Promise<boolean>,
|
|
30
|
+
/**
|
|
31
|
+
* Do the search and return a list of ids.
|
|
32
|
+
* @param props
|
|
33
|
+
*/
|
|
34
|
+
search: (props: {
|
|
35
|
+
searchString: string,
|
|
36
|
+
path: string,
|
|
37
|
+
currentUser?: FirebaseUser,
|
|
38
|
+
databaseId?: string,
|
|
39
|
+
collection?: EntityCollection
|
|
40
|
+
}) => (Promise<readonly string[] | undefined>),
|
|
41
|
+
|
|
42
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildExternalSearchController } from "./text_search_controller";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility function to perform a text search in an algolia index,
|
|
5
|
+
* returning the ids of the entities.
|
|
6
|
+
* @param client The algolia client
|
|
7
|
+
* @param indexName
|
|
8
|
+
* @param query
|
|
9
|
+
* @group Firebase
|
|
10
|
+
*/
|
|
11
|
+
export function performAlgoliaTextSearch(client: any, indexName: string, query: string): Promise<readonly string[]> {
|
|
12
|
+
|
|
13
|
+
console.debug("Performing Algolia query", client, query);
|
|
14
|
+
|
|
15
|
+
return client.searchSingleIndex({
|
|
16
|
+
indexName,
|
|
17
|
+
searchParams: { query },
|
|
18
|
+
}).then(({ hits }: any) => {
|
|
19
|
+
return hits.map((hit: any) => hit.objectID as string);
|
|
20
|
+
})
|
|
21
|
+
.catch((err: any) => {
|
|
22
|
+
console.error(err);
|
|
23
|
+
return [];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { deleteField, DocumentSnapshot } from "@firebase/firestore";
|
|
2
|
+
import { EntityCollection, FirebaseCollection, Properties, Property } from "@rebasepro/types";
|
|
3
|
+
import { COLLECTION_PATH_SEPARATOR, sortProperties, stripCollectionPath } from "@rebasepro/common";
|
|
4
|
+
|
|
5
|
+
export function buildCollectionId(idOrPath: string, parentCollectionIds?: string[]): string {
|
|
6
|
+
if (!parentCollectionIds)
|
|
7
|
+
return stripCollectionPath(idOrPath);
|
|
8
|
+
return [...parentCollectionIds.map(stripCollectionPath), stripCollectionPath(idOrPath)].join(COLLECTION_PATH_SEPARATOR);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export const docsToCollectionTree = (docs: DocumentSnapshot[]): EntityCollection[] => {
|
|
14
|
+
|
|
15
|
+
const collectionsMap = docs.map((doc) => {
|
|
16
|
+
const id: string = doc.id;
|
|
17
|
+
const collection = docToCollection(doc);
|
|
18
|
+
return { [id]: collection };
|
|
19
|
+
}).reduce((a, b) => ({ ...a, ...b }), {});
|
|
20
|
+
|
|
21
|
+
const orderedKeys = Object.keys(collectionsMap).sort((a, b) => b.split(COLLECTION_PATH_SEPARATOR).length - a.split(COLLECTION_PATH_SEPARATOR).length);
|
|
22
|
+
|
|
23
|
+
orderedKeys.forEach((id) => {
|
|
24
|
+
const collection = collectionsMap[id];
|
|
25
|
+
if (id.includes(COLLECTION_PATH_SEPARATOR)) {
|
|
26
|
+
const parentId = id.split(COLLECTION_PATH_SEPARATOR).slice(0, -1).join(COLLECTION_PATH_SEPARATOR);
|
|
27
|
+
const parentCollection = collectionsMap[parentId];
|
|
28
|
+
if (parentCollection)
|
|
29
|
+
(parentCollection as FirebaseCollection).subcollections = () => [...((parentCollection as FirebaseCollection).subcollections?.() ?? []), collection];
|
|
30
|
+
delete collectionsMap[id];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return Object.values(collectionsMap);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const docToCollection = (doc: DocumentSnapshot): EntityCollection => {
|
|
38
|
+
const data = doc.data();
|
|
39
|
+
if (!data)
|
|
40
|
+
throw Error("Entity collection has not been persisted correctly");
|
|
41
|
+
const propertiesOrder = data.propertiesOrder;
|
|
42
|
+
const properties = data.properties as Properties ?? {};
|
|
43
|
+
|
|
44
|
+
// Normalize enum values from object format to array format (sorted alphabetically)
|
|
45
|
+
const normalizedProperties = normalizePropertiesEnumValues(properties, true);
|
|
46
|
+
const sortedProperties = sortProperties(normalizedProperties, propertiesOrder);
|
|
47
|
+
return {
|
|
48
|
+
...data,
|
|
49
|
+
properties: sortedProperties,
|
|
50
|
+
slug: data.id ?? data.alias ?? data.slug
|
|
51
|
+
} as EntityCollection;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Converts enum values from object format to array format.
|
|
58
|
+
* Firestore doesn't preserve object key order, so we must use arrays.
|
|
59
|
+
* When enum values are already stored as an array, their order is preserved
|
|
60
|
+
* (this is intentional - users can reorder columns in Kanban view).
|
|
61
|
+
* Only sort alphabetically when converting from legacy object format.
|
|
62
|
+
* @param enumValues - The enum values (object or array format)
|
|
63
|
+
* @param sortObjectFormat - If true, sort by id alphabetically when converting from object format
|
|
64
|
+
* @returns Array of EnumValueConfig objects
|
|
65
|
+
*/
|
|
66
|
+
function normalizeEnumValuesToArray(
|
|
67
|
+
enumValues: unknown,
|
|
68
|
+
sortObjectFormat: boolean = false
|
|
69
|
+
): unknown[] {
|
|
70
|
+
if (Array.isArray(enumValues)) {
|
|
71
|
+
// Already an array - preserve order! This order is intentional
|
|
72
|
+
// (e.g., user reordered Kanban columns)
|
|
73
|
+
return enumValues;
|
|
74
|
+
} else if (typeof enumValues === "object" && enumValues !== null) {
|
|
75
|
+
// Convert object to array format
|
|
76
|
+
// Object keys don't have guaranteed order in Firestore, so we sort alphabetically
|
|
77
|
+
const entries = Object.entries(enumValues).map(([id, value]) =>
|
|
78
|
+
typeof value === "string"
|
|
79
|
+
? {
|
|
80
|
+
id,
|
|
81
|
+
label: value
|
|
82
|
+
}
|
|
83
|
+
: {
|
|
84
|
+
...(value as object),
|
|
85
|
+
id
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
// Sort alphabetically by id when loading from Firestore object format
|
|
89
|
+
// This is the only case where sorting makes sense, since object key order is not preserved
|
|
90
|
+
if (sortObjectFormat) {
|
|
91
|
+
entries.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
92
|
+
}
|
|
93
|
+
return entries;
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Normalizes all enum values in a properties object.
|
|
100
|
+
* @param properties - The properties object to normalize
|
|
101
|
+
* @param sortObjectFormat - If true, sort enum values alphabetically when converting from object format
|
|
102
|
+
* @returns Properties with normalized enum values
|
|
103
|
+
*/
|
|
104
|
+
function normalizePropertiesEnumValues(
|
|
105
|
+
properties: Properties,
|
|
106
|
+
sortObjectFormat: boolean = false
|
|
107
|
+
): Properties {
|
|
108
|
+
const result: Properties = {};
|
|
109
|
+
Object.entries(properties).forEach(([key, property]) => {
|
|
110
|
+
if (typeof property === "object" && property !== null) {
|
|
111
|
+
const normalizedProperty = { ...property } as Record<string, unknown>;
|
|
112
|
+
|
|
113
|
+
// Handle direct enum values
|
|
114
|
+
if (normalizedProperty.enum) {
|
|
115
|
+
normalizedProperty.enum = normalizeEnumValuesToArray(
|
|
116
|
+
normalizedProperty.enum,
|
|
117
|
+
sortObjectFormat
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle array properties with enum values in "of"
|
|
122
|
+
if (normalizedProperty.dataType === "array" && typeof normalizedProperty.of === "object" && normalizedProperty.of !== null) {
|
|
123
|
+
const ofProp = normalizedProperty.of as Record<string, unknown>;
|
|
124
|
+
if (ofProp.enum) {
|
|
125
|
+
normalizedProperty.of = {
|
|
126
|
+
...ofProp,
|
|
127
|
+
enum: normalizeEnumValuesToArray(
|
|
128
|
+
ofProp.enum,
|
|
129
|
+
sortObjectFormat
|
|
130
|
+
)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle map properties recursively
|
|
136
|
+
if (normalizedProperty.dataType === "map" && normalizedProperty.properties) {
|
|
137
|
+
normalizedProperty.properties = normalizePropertiesEnumValues(
|
|
138
|
+
normalizedProperty.properties as Properties,
|
|
139
|
+
sortObjectFormat
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
result[key] = normalizedProperty as unknown as Property;
|
|
144
|
+
} else {
|
|
145
|
+
result[key] = property;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FirebaseApp } from "@firebase/app";
|
|
2
|
+
import {
|
|
3
|
+
collection,
|
|
4
|
+
getDocs,
|
|
5
|
+
getFirestore,
|
|
6
|
+
limit as limitClause,
|
|
7
|
+
query,
|
|
8
|
+
QueryDocumentSnapshot
|
|
9
|
+
} from "@firebase/firestore";
|
|
10
|
+
|
|
11
|
+
export async function getFirestoreDataInPath(firebaseApp: FirebaseApp, path: string, parentPaths: string[], limit: number): Promise<object[]> {
|
|
12
|
+
const firestore = getFirestore(firebaseApp);
|
|
13
|
+
if (!parentPaths || parentPaths.length === 0) {
|
|
14
|
+
const q = query(collection(firestore, path), limitClause(limit));
|
|
15
|
+
return getDocs(q).then((querySnapshot) => {
|
|
16
|
+
return querySnapshot.docs.map(doc => doc.data());
|
|
17
|
+
});
|
|
18
|
+
} else {
|
|
19
|
+
let currentDocs: QueryDocumentSnapshot[] | undefined = undefined;
|
|
20
|
+
let index = 0;
|
|
21
|
+
const allPaths = parentPaths;
|
|
22
|
+
allPaths.push(path);
|
|
23
|
+
let parentPath: string | undefined = allPaths[0];
|
|
24
|
+
while (parentPath) {
|
|
25
|
+
if (currentDocs) {
|
|
26
|
+
currentDocs = (await Promise.all(currentDocs.map(async (doc) => {
|
|
27
|
+
const q = query(collection(firestore, doc.ref.path, parentPath as string), limitClause(5));
|
|
28
|
+
return (await getDocs(q)).docs;
|
|
29
|
+
}))).flat();
|
|
30
|
+
} else {
|
|
31
|
+
const q = query(collection(firestore, parentPath), limitClause(5));
|
|
32
|
+
currentDocs = (await getDocs(q)).docs;
|
|
33
|
+
}
|
|
34
|
+
index++;
|
|
35
|
+
parentPath = index < allPaths.length ? allPaths[index] : undefined;
|
|
36
|
+
}
|
|
37
|
+
return currentDocs ? currentDocs.map(doc => doc.data()) : [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./collections_firestore";
|
|
2
|
+
export * from "./database";
|
|
3
|
+
export * from "./algolia";
|
|
4
|
+
export * from "./pinecone";
|
|
5
|
+
export * from "./text_search_controller";
|
|
6
|
+
export * from "./local_text_search_controller";
|
|
7
|
+
export * from "./rebase_search_controller";
|