@rebasepro/client-firebase 0.2.1 → 0.2.4
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/components/RebaseFirebaseApp.d.ts +1 -1
- package/dist/components/RebaseFirebaseAppProps.d.ts +5 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAppCheck.d.ts +1 -1
- package/dist/hooks/useBuildUserManagement.d.ts +2 -7
- package/dist/hooks/useFirebaseAccessGate.d.ts +44 -0
- package/dist/hooks/useFirestoreDriver.d.ts +2 -2
- package/dist/index.es.js +159 -35
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +158 -34
- package/dist/index.umd.js.map +1 -1
- package/dist/utils/algolia.d.ts +7 -1
- package/package.json +13 -8
- package/src/components/FirebaseLoginView.tsx +1 -1
- package/src/components/RebaseFirebaseApp.tsx +9 -9
- package/src/components/RebaseFirebaseAppProps.tsx +5 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAppCheck.ts +4 -4
- package/src/hooks/useBuildUserManagement.tsx +14 -22
- package/src/hooks/useFirebaseAccessGate.tsx +145 -0
- package/src/hooks/useFirebaseRealTimeDBDelegate.ts +17 -12
- package/src/hooks/useFirebaseStorageSource.ts +4 -4
- package/src/hooks/useFirestoreDriver.ts +27 -23
- package/src/hooks/useInitialiseFirebase.ts +2 -2
- package/src/utils/algolia.ts +4 -4
- package/src/utils/local_text_search_controller.ts +1 -1
- package/src/utils/rebase_search_controller.ts +14 -14
package/dist/utils/algolia.d.ts
CHANGED
|
@@ -6,4 +6,10 @@
|
|
|
6
6
|
* @param query
|
|
7
7
|
* @group Firebase
|
|
8
8
|
*/
|
|
9
|
-
export declare function performAlgoliaTextSearch(client:
|
|
9
|
+
export declare function performAlgoliaTextSearch(client: {
|
|
10
|
+
searchSingleIndex: (params: Record<string, unknown>) => Promise<{
|
|
11
|
+
hits: Array<{
|
|
12
|
+
objectID: string;
|
|
13
|
+
}>;
|
|
14
|
+
}>;
|
|
15
|
+
}, indexName: string, query: string): Promise<readonly string[]>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/client-firebase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"fast-equals": "6.0.0",
|
|
15
15
|
"fuse.js": "^7.3.0",
|
|
16
16
|
"react-router-dom": "^7.15.1",
|
|
17
|
-
"@rebasepro/admin": "0.2.
|
|
18
|
-
"@rebasepro/
|
|
19
|
-
"@rebasepro/
|
|
20
|
-
"@rebasepro/types": "0.2.
|
|
21
|
-
"@rebasepro/ui": "0.2.
|
|
22
|
-
"@rebasepro/utils": "0.2.
|
|
17
|
+
"@rebasepro/admin": "0.2.4",
|
|
18
|
+
"@rebasepro/common": "0.2.4",
|
|
19
|
+
"@rebasepro/core": "0.2.4",
|
|
20
|
+
"@rebasepro/types": "0.2.4",
|
|
21
|
+
"@rebasepro/ui": "0.2.4",
|
|
22
|
+
"@rebasepro/utils": "0.2.4"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"firebase": "^10.12.2 || ^11.0.0 || ^12.0.0",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@vitejs/plugin-react": "^4.7.0",
|
|
56
56
|
"babel-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411",
|
|
57
57
|
"firebase": "^12.13.0",
|
|
58
|
-
"typesense": "^2.1.0",
|
|
59
58
|
"typescript": "^5.9.3",
|
|
59
|
+
"typesense": "^2.1.0",
|
|
60
60
|
"vite": "^7.3.3"
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
"src"
|
|
65
65
|
],
|
|
66
66
|
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
70
|
+
"directory": "packages/client-firebase"
|
|
71
|
+
},
|
|
67
72
|
"scripts": {
|
|
68
73
|
"dev": "vite",
|
|
69
74
|
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
|
|
@@ -261,7 +261,7 @@ export function FirebaseLoginView({
|
|
|
261
261
|
} else if (notAllowedError instanceof Error) {
|
|
262
262
|
notAllowedMessage = notAllowedError.message;
|
|
263
263
|
} else {
|
|
264
|
-
notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified
|
|
264
|
+
notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified access gate configuration";
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
useBuildModeController,
|
|
11
11
|
useBuildAdminModeController,
|
|
12
12
|
AdminModeControllerProvider,
|
|
13
|
-
useValidateAuthenticator,
|
|
14
13
|
RebaseRoutes
|
|
15
14
|
} from "@rebasepro/core";
|
|
16
15
|
import {
|
|
@@ -27,7 +26,7 @@ import {
|
|
|
27
26
|
SideEntityProvider,
|
|
28
27
|
RebaseRoute
|
|
29
28
|
} from "@rebasepro/admin";
|
|
30
|
-
import { Entity, PropertyConfig, UserManagementDelegate } from "@rebasepro/types";
|
|
29
|
+
import { Entity, PropertyConfig, RebaseContext, UserManagementDelegate } from "@rebasepro/types";
|
|
31
30
|
import { CenteredView, CircularProgressCenter } from "@rebasepro/ui";
|
|
32
31
|
import { buildRebaseData } from "@rebasepro/common";
|
|
33
32
|
import { Route, Outlet, Navigate } from "react-router-dom";
|
|
@@ -40,7 +39,8 @@ import {
|
|
|
40
39
|
useFirebaseStorageSource,
|
|
41
40
|
useFirestoreDriver,
|
|
42
41
|
useInitialiseFirebase,
|
|
43
|
-
useBuildUserManagement
|
|
42
|
+
useBuildUserManagement,
|
|
43
|
+
useFirebaseAccessGate
|
|
44
44
|
} from "../hooks";
|
|
45
45
|
|
|
46
46
|
import { FirebaseAuthController } from "../types";
|
|
@@ -70,7 +70,7 @@ export function RebaseFirebaseApp({
|
|
|
70
70
|
name,
|
|
71
71
|
logo,
|
|
72
72
|
logoDark,
|
|
73
|
-
|
|
73
|
+
accessGate,
|
|
74
74
|
collections,
|
|
75
75
|
views,
|
|
76
76
|
adminViews,
|
|
@@ -158,15 +158,15 @@ export function RebaseFirebaseApp({
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
* Validate
|
|
161
|
+
* Validate access gate
|
|
162
162
|
*/
|
|
163
163
|
const {
|
|
164
164
|
authLoading,
|
|
165
165
|
canAccessMainView,
|
|
166
166
|
notAllowedError
|
|
167
|
-
} =
|
|
167
|
+
} = useFirebaseAccessGate({
|
|
168
168
|
authController,
|
|
169
|
-
|
|
169
|
+
accessGate,
|
|
170
170
|
data: buildRebaseData(firestoreDelegate),
|
|
171
171
|
storageSource
|
|
172
172
|
});
|
|
@@ -234,7 +234,7 @@ export function RebaseFirebaseApp({
|
|
|
234
234
|
{({
|
|
235
235
|
context,
|
|
236
236
|
loading
|
|
237
|
-
}: { context:
|
|
237
|
+
}: { context: RebaseContext, loading: boolean }) => {
|
|
238
238
|
|
|
239
239
|
let component;
|
|
240
240
|
if (loading || authLoading) {
|
|
@@ -250,7 +250,7 @@ export function RebaseFirebaseApp({
|
|
|
250
250
|
signInOptions={signInOptions ?? DEFAULT_SIGN_IN_OPTIONS}
|
|
251
251
|
firebaseApp={firebaseApp}
|
|
252
252
|
authController={authController}
|
|
253
|
-
notAllowedError={notAllowedError}/>
|
|
253
|
+
notAllowedError={notAllowedError instanceof Error ? notAllowedError : typeof notAllowedError === "string" ? notAllowedError : undefined}/>
|
|
254
254
|
);
|
|
255
255
|
} else {
|
|
256
256
|
const firstCollectionEntry = navigationStateController.topLevelNavigation?.navigationEntries.find(e => e.type === "collection");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { AnalyticsEvent, AppView, AppViewsBuilder, EntityCollection, EntityCollectionsBuilder, RebasePlugin, Locale, PropertyConfig } from "@rebasepro/types";
|
|
4
|
+
import { FirebaseAccessGate } from "../hooks/useFirebaseAccessGate";
|
|
4
5
|
import { UserManagementDelegate } from "@rebasepro/types";
|
|
5
6
|
import { FirebaseApp } from "@firebase/app";
|
|
6
7
|
import { FirebaseLoginViewProps } from "./FirebaseLoginView";
|
|
@@ -64,12 +65,12 @@ export type RebaseFirebaseAppProps = {
|
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Do the users need to log in to access the CMS.
|
|
67
|
-
* You can specify
|
|
68
|
-
* access the CMS or not.
|
|
68
|
+
* You can specify a {@link FirebaseAccessGate} function to discriminate
|
|
69
|
+
* which users can access the CMS or not.
|
|
69
70
|
* If not specified, authentication is enabled but no user restrictions
|
|
70
71
|
* apply
|
|
71
72
|
*/
|
|
72
|
-
|
|
73
|
+
accessGate?: boolean | FirebaseAccessGate<FirebaseUserWrapper>;
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* List of sign in options that will be displayed in the login
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useAppCheck.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface InitializeAppCheckProps {
|
|
|
15
15
|
export interface InitializeAppCheckResult {
|
|
16
16
|
loading: boolean;
|
|
17
17
|
appCheckVerified?: boolean;
|
|
18
|
-
error?:
|
|
18
|
+
error?: unknown;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -35,7 +35,7 @@ export function useAppCheck({
|
|
|
35
35
|
|
|
36
36
|
const [appCheckLoading, setAppCheckLoading] = React.useState<boolean>(false);
|
|
37
37
|
const [appCheckVerified, setAppCheckVerified] = React.useState<boolean | undefined>(undefined);
|
|
38
|
-
const [error, setError] = React.useState<
|
|
38
|
+
const [error, setError] = React.useState<unknown>();
|
|
39
39
|
|
|
40
40
|
const initialCheck = useRef<boolean>(false);
|
|
41
41
|
|
|
@@ -51,9 +51,9 @@ export function useAppCheck({
|
|
|
51
51
|
setAppCheckVerified(true);
|
|
52
52
|
console.debug("App Check success.");
|
|
53
53
|
}
|
|
54
|
-
} catch (e:
|
|
54
|
+
} catch (e: unknown) {
|
|
55
55
|
console.error("App Check error:", e);
|
|
56
|
-
setError(e.message);
|
|
56
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
57
57
|
}
|
|
58
58
|
}, [options?.forceRefresh]);
|
|
59
59
|
|
|
@@ -4,17 +4,17 @@ import { deepEqual as equal } from "fast-equals";
|
|
|
4
4
|
import { removeUndefined } from "@rebasepro/utils";
|
|
5
5
|
import {
|
|
6
6
|
AuthController,
|
|
7
|
-
Authenticator,
|
|
8
7
|
DataDriver,
|
|
9
8
|
Entity,
|
|
10
9
|
Role,
|
|
11
10
|
User,
|
|
12
11
|
UserManagementDelegate
|
|
13
12
|
} from "@rebasepro/types";
|
|
13
|
+
import { FirebaseAccessGate } from "./useFirebaseAccessGate";
|
|
14
14
|
|
|
15
|
-
type UserWithRoleIds<USER extends User =
|
|
15
|
+
type UserWithRoleIds<USER extends User = User> = Omit<USER, "roles"> & { roles: string[] };
|
|
16
16
|
|
|
17
|
-
export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
17
|
+
export interface UserManagementDelegateParams<CONTROLLER extends AuthController<User> = AuthController<User>> {
|
|
18
18
|
|
|
19
19
|
authController: CONTROLLER;
|
|
20
20
|
|
|
@@ -49,11 +49,6 @@ export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
|
49
49
|
*/
|
|
50
50
|
allowDefaultRolesCreation?: boolean;
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Include the collection config permissions in the user management system.
|
|
54
|
-
*/
|
|
55
|
-
includeCollectionConfigPermissions?: boolean;
|
|
56
|
-
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
/**
|
|
@@ -65,18 +60,16 @@ export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
|
65
60
|
* @param rolesPath
|
|
66
61
|
* @param roles
|
|
67
62
|
* @param allowDefaultRolesCreation
|
|
68
|
-
* @param includeCollectionConfigPermissions
|
|
69
63
|
*/
|
|
70
|
-
export function useBuildUserManagement<CONTROLLER extends AuthController<
|
|
71
|
-
USER extends User = CONTROLLER extends AuthController<infer U> ? U :
|
|
64
|
+
export function useBuildUserManagement<CONTROLLER extends AuthController<User> = AuthController<User>,
|
|
65
|
+
USER extends User = CONTROLLER extends AuthController<infer U> ? U : User>
|
|
72
66
|
({
|
|
73
67
|
authController,
|
|
74
68
|
dataSourceDelegate,
|
|
75
69
|
roles: rolesProp,
|
|
76
70
|
usersPath = "__FIRECMS/config/users",
|
|
77
71
|
rolesPath = "__FIRECMS/config/roles",
|
|
78
|
-
allowDefaultRolesCreation
|
|
79
|
-
includeCollectionConfigPermissions
|
|
72
|
+
allowDefaultRolesCreation
|
|
80
73
|
}: UserManagementDelegateParams<CONTROLLER>): UserManagementDelegate<USER> & CONTROLLER {
|
|
81
74
|
|
|
82
75
|
if (!authController) {
|
|
@@ -125,10 +118,10 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
125
118
|
}
|
|
126
119
|
setRolesLoading(false);
|
|
127
120
|
},
|
|
128
|
-
onError(e:
|
|
121
|
+
onError(e: unknown): void {
|
|
129
122
|
setRoles([]);
|
|
130
123
|
console.error("Error loading roles", e);
|
|
131
|
-
setRolesError(e);
|
|
124
|
+
setRolesError(e instanceof Error ? e : new Error(String(e)));
|
|
132
125
|
setRolesLoading(false);
|
|
133
126
|
}
|
|
134
127
|
});
|
|
@@ -151,7 +144,7 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
151
144
|
console.debug("Updating users", entities);
|
|
152
145
|
setUsersError(undefined);
|
|
153
146
|
try {
|
|
154
|
-
const newUsers = entitiesToUsers(entities);
|
|
147
|
+
const newUsers = entitiesToUsers(entities) as UserWithRoleIds<USER>[];
|
|
155
148
|
// if (!equal(newUsers, usersWithRoleIds))
|
|
156
149
|
setUsersWithRoleIds(newUsers);
|
|
157
150
|
} catch (e) {
|
|
@@ -161,10 +154,10 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
161
154
|
}
|
|
162
155
|
setUsersLoading(false);
|
|
163
156
|
},
|
|
164
|
-
onError(e:
|
|
157
|
+
onError(e: unknown): void {
|
|
165
158
|
console.error("Error loading users", e);
|
|
166
159
|
setUsersWithRoleIds([]);
|
|
167
|
-
setUsersError(e);
|
|
160
|
+
setUsersError(e instanceof Error ? e : new Error(String(e)));
|
|
168
161
|
setUsersLoading(false);
|
|
169
162
|
}
|
|
170
163
|
});
|
|
@@ -268,7 +261,7 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
268
261
|
return roles.filter(r => mgmtUser.roles.includes(r.id));
|
|
269
262
|
}, [roles, usersWithRoleIds]);
|
|
270
263
|
|
|
271
|
-
const
|
|
264
|
+
const accessGate: FirebaseAccessGate<USER> = useCallback(({ user }) => {
|
|
272
265
|
|
|
273
266
|
if (loading) {
|
|
274
267
|
return false;
|
|
@@ -339,9 +332,8 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
339
332
|
usersError,
|
|
340
333
|
isAdmin,
|
|
341
334
|
allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
|
|
342
|
-
includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
|
|
343
335
|
defineRolesFor,
|
|
344
|
-
|
|
336
|
+
accessGate,
|
|
345
337
|
...authController,
|
|
346
338
|
initialLoading: authController.initialLoading || loading,
|
|
347
339
|
userRoles: userRoles,
|
|
@@ -358,8 +350,8 @@ const entitiesToUsers = (docs: Entity<Omit<UserWithRoleIds, "id">>[]): (UserWith
|
|
|
358
350
|
const data = doc.values;
|
|
359
351
|
const record = data as Record<string, unknown>;
|
|
360
352
|
const newVar = {
|
|
361
|
-
uid: doc.id,
|
|
362
353
|
...data,
|
|
354
|
+
uid: doc.id,
|
|
363
355
|
created_on: record.created_on as Date | undefined,
|
|
364
356
|
updated_on: record.updated_on as Date | undefined
|
|
365
357
|
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
|
3
|
+
import { deepEqual as equal } from "fast-equals";
|
|
4
|
+
|
|
5
|
+
import { AuthController, RebaseData, StorageSource, User } from "@rebasepro/types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side gate that decides whether a Firebase-authenticated user
|
|
9
|
+
* is allowed to access the CMS UI.
|
|
10
|
+
*
|
|
11
|
+
* Return `true` to allow access or `false` / throw to deny.
|
|
12
|
+
* @group Firebase
|
|
13
|
+
*/
|
|
14
|
+
export type FirebaseAccessGate<USER extends User = User> = (props: {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Logged-in user or null
|
|
18
|
+
*/
|
|
19
|
+
user: USER | null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* AuthController
|
|
23
|
+
*/
|
|
24
|
+
authController: AuthController<USER>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Unified data access API
|
|
28
|
+
*/
|
|
29
|
+
data: RebaseData;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Used storage implementation
|
|
33
|
+
*/
|
|
34
|
+
storageSource: StorageSource;
|
|
35
|
+
|
|
36
|
+
}) => boolean | Promise<boolean>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook that evaluates a {@link FirebaseAccessGate} callback after
|
|
40
|
+
* the user logs in and gates access to the main CMS view.
|
|
41
|
+
*
|
|
42
|
+
* @group Firebase
|
|
43
|
+
*/
|
|
44
|
+
export function useFirebaseAccessGate<USER extends User = User>
|
|
45
|
+
({
|
|
46
|
+
disabled,
|
|
47
|
+
authController,
|
|
48
|
+
accessGate,
|
|
49
|
+
storageSource,
|
|
50
|
+
data
|
|
51
|
+
}:
|
|
52
|
+
{
|
|
53
|
+
disabled?: boolean,
|
|
54
|
+
authController: AuthController<USER>,
|
|
55
|
+
accessGate?: boolean | FirebaseAccessGate<USER>,
|
|
56
|
+
data: RebaseData;
|
|
57
|
+
storageSource: StorageSource;
|
|
58
|
+
}): {
|
|
59
|
+
canAccessMainView: boolean,
|
|
60
|
+
authLoading: boolean,
|
|
61
|
+
notAllowedError: unknown,
|
|
62
|
+
authVerified: boolean,
|
|
63
|
+
} {
|
|
64
|
+
|
|
65
|
+
const gateEnabled = Boolean(accessGate);
|
|
66
|
+
|
|
67
|
+
const [authLoading, setAuthLoading] = useState<boolean>(gateEnabled);
|
|
68
|
+
const [notAllowedError, setNotAllowedError] = useState<unknown>(false);
|
|
69
|
+
const [authVerified, setAuthVerified] = useState<boolean>(!gateEnabled || Boolean(authController.loginSkipped));
|
|
70
|
+
|
|
71
|
+
const canAccessMainView = (authVerified) &&
|
|
72
|
+
(!gateEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
|
|
73
|
+
!notAllowedError;
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (authController.loginSkipped)
|
|
77
|
+
setAuthVerified(true);
|
|
78
|
+
}, [authController.loginSkipped]);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* We use this ref to check the authentication only if the user has
|
|
82
|
+
* changed.
|
|
83
|
+
*/
|
|
84
|
+
const checkedUserRef = useRef<User | undefined>(undefined);
|
|
85
|
+
|
|
86
|
+
const checkAccess = useCallback(async () => {
|
|
87
|
+
|
|
88
|
+
if (disabled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (authController.initialLoading) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!authController.user && !authController.loginSkipped) {
|
|
97
|
+
checkedUserRef.current = undefined;
|
|
98
|
+
setAuthLoading(false);
|
|
99
|
+
setAuthVerified(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const delegateUser = authController.user;
|
|
104
|
+
|
|
105
|
+
if (accessGate instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
106
|
+
setAuthLoading(true);
|
|
107
|
+
try {
|
|
108
|
+
const allowed = await accessGate({
|
|
109
|
+
user: delegateUser,
|
|
110
|
+
authController,
|
|
111
|
+
data,
|
|
112
|
+
storageSource
|
|
113
|
+
});
|
|
114
|
+
if (!allowed) {
|
|
115
|
+
authController.signOut();
|
|
116
|
+
setNotAllowedError(true);
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
setNotAllowedError(e);
|
|
120
|
+
authController.signOut();
|
|
121
|
+
}
|
|
122
|
+
setAuthLoading(false);
|
|
123
|
+
setAuthVerified(true);
|
|
124
|
+
checkedUserRef.current = delegateUser;
|
|
125
|
+
} else {
|
|
126
|
+
setAuthLoading(false);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!authController.initialLoading && !delegateUser) {
|
|
130
|
+
setAuthVerified(true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}, [disabled, authController, accessGate, data, storageSource]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
checkAccess();
|
|
137
|
+
}, [checkAccess]);
|
|
138
|
+
|
|
139
|
+
return useMemo(() => ({
|
|
140
|
+
canAccessMainView,
|
|
141
|
+
authLoading: gateEnabled && authLoading,
|
|
142
|
+
notAllowedError,
|
|
143
|
+
authVerified
|
|
144
|
+
}), [canAccessMainView, gateEnabled, authLoading, notAllowedError, authVerified]);
|
|
145
|
+
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
startAt
|
|
16
16
|
} from "@firebase/database";
|
|
17
17
|
import { useCallback } from "react";
|
|
18
|
-
import { DataDriver, DeleteEntityProps, Entity, FetchCollectionProps, FetchEntityProps, ListenCollectionProps, ListenEntityProps, SaveEntityProps } from "@rebasepro/types";
|
|
18
|
+
import { DataDriver, DeleteEntityProps, Entity, EntityCollection, FetchCollectionProps, FetchEntityProps, FilterValues, ListenCollectionProps, ListenEntityProps, SaveEntityProps } from "@rebasepro/types";
|
|
19
19
|
|
|
20
20
|
export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: FirebaseApp }): DataDriver {
|
|
21
21
|
|
|
@@ -167,14 +167,14 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
167
167
|
}, [firebaseApp]);
|
|
168
168
|
|
|
169
169
|
// Implementing additional methods required by DataDriver
|
|
170
|
-
const checkUniqueField = useCallback(async (slug: string, name: string, value:
|
|
170
|
+
const checkUniqueField = useCallback(async (slug: string, name: string, value: unknown, entityId?: string | number): Promise<boolean> => {
|
|
171
171
|
if (!firebaseApp) {
|
|
172
172
|
throw new Error("Firebase app not provided");
|
|
173
173
|
}
|
|
174
174
|
const database = getDatabase(firebaseApp);
|
|
175
175
|
|
|
176
176
|
// Simplified example; the Realtime Database does not support querying with "not equal" conditions
|
|
177
|
-
const dbRef = query(ref(database, slug), orderByChild(name), startAt(value), limitToFirst(1));
|
|
177
|
+
const dbRef = query(ref(database, slug), orderByChild(name), startAt(value as string | number | boolean | null), limitToFirst(1));
|
|
178
178
|
const snapshot = await get(dbRef);
|
|
179
179
|
|
|
180
180
|
if (!snapshot.exists()) {
|
|
@@ -194,7 +194,11 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
194
194
|
path,
|
|
195
195
|
filter,
|
|
196
196
|
sortBy
|
|
197
|
-
}:
|
|
197
|
+
}: {
|
|
198
|
+
path: string;
|
|
199
|
+
filter?: FilterValues<string>;
|
|
200
|
+
sortBy?: [string, "asc" | "desc"];
|
|
201
|
+
}): boolean => {
|
|
198
202
|
return false;
|
|
199
203
|
}, []);
|
|
200
204
|
|
|
@@ -217,7 +221,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
217
221
|
* Transform data from RTDB format back to CMS format
|
|
218
222
|
* This is used internally when fetching/listening to data
|
|
219
223
|
*/
|
|
220
|
-
function delegateToCMSModel(data:
|
|
224
|
+
function delegateToCMSModel(data: unknown): unknown {
|
|
221
225
|
if (data === null || data === undefined) return null;
|
|
222
226
|
|
|
223
227
|
if (Array.isArray(data)) {
|
|
@@ -225,9 +229,9 @@ function delegateToCMSModel(data: any): any {
|
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
if (typeof data === "object") {
|
|
228
|
-
const result: Record<string,
|
|
229
|
-
for (const key of Object.keys(data)) {
|
|
230
|
-
const childValue = delegateToCMSModel(data[key]);
|
|
232
|
+
const result: Record<string, unknown> = {};
|
|
233
|
+
for (const key of Object.keys(data as Record<string, unknown>)) {
|
|
234
|
+
const childValue = delegateToCMSModel((data as Record<string, unknown>)[key]);
|
|
231
235
|
if (childValue !== undefined)
|
|
232
236
|
result[key] = childValue;
|
|
233
237
|
}
|
|
@@ -241,20 +245,21 @@ function delegateToCMSModel(data: any): any {
|
|
|
241
245
|
* Transform data from CMS format to RTDB format
|
|
242
246
|
* This is used internally when saving data
|
|
243
247
|
*/
|
|
244
|
-
function cmsToRTDBModel(data:
|
|
248
|
+
function cmsToRTDBModel(data: unknown, database: Database): unknown {
|
|
245
249
|
if (data === undefined) {
|
|
246
250
|
return null;
|
|
247
251
|
} else if (data === null) {
|
|
248
252
|
return null;
|
|
249
253
|
} else if (Array.isArray(data)) {
|
|
250
254
|
return data.filter(v => v !== undefined).map(v => cmsToRTDBModel(v, database));
|
|
251
|
-
} else if (data.isEntityReference && data.isEntityReference()) {
|
|
252
|
-
|
|
255
|
+
} else if (typeof data === "object" && data !== null && "isEntityReference" in data && typeof (data as Record<string, unknown>).isEntityReference === "function" && (data as { isEntityReference: () => boolean }).isEntityReference()) {
|
|
256
|
+
const entityRef = data as unknown as { slug: string; id: string };
|
|
257
|
+
return ref(database, `${entityRef.slug}/${entityRef.id}`);
|
|
253
258
|
} else if (data instanceof Date) {
|
|
254
259
|
// For dates, convert to ISO string or timestamp.
|
|
255
260
|
return data.toISOString();
|
|
256
261
|
} else if (data && typeof data === "object") {
|
|
257
|
-
return Object.entries(data)
|
|
262
|
+
return Object.entries(data as Record<string, unknown>)
|
|
258
263
|
.map(([key, v]) => {
|
|
259
264
|
const rtdbModel = cmsToRTDBModel(v, database);
|
|
260
265
|
if (rtdbModel !== undefined)
|
|
@@ -132,8 +132,8 @@ export function useFirebaseStorageSource({
|
|
|
132
132
|
const response = await fetch(url);
|
|
133
133
|
const blob = await response.blob();
|
|
134
134
|
return new File([blob], path);
|
|
135
|
-
} catch (e:
|
|
136
|
-
if (e
|
|
135
|
+
} catch (e: unknown) {
|
|
136
|
+
if (typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "storage/object-not-found") return null;
|
|
137
137
|
throw e;
|
|
138
138
|
}
|
|
139
139
|
},
|
|
@@ -170,8 +170,8 @@ export function useFirebaseStorageSource({
|
|
|
170
170
|
}
|
|
171
171
|
urlsCache[storagePathOrUrl] = result;
|
|
172
172
|
return result;
|
|
173
|
-
} catch (e:
|
|
174
|
-
if (e
|
|
173
|
+
} catch (e: unknown) {
|
|
174
|
+
if (typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "storage/object-not-found") return {
|
|
175
175
|
url: null,
|
|
176
176
|
fileNotFound: true
|
|
177
177
|
};
|