@rebasepro/client-firebase 0.2.3 → 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.
@@ -6,4 +6,10 @@
6
6
  * @param query
7
7
  * @group Firebase
8
8
  */
9
- export declare function performAlgoliaTextSearch(client: any, indexName: string, query: string): Promise<readonly string[]>;
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.3",
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.3",
18
- "@rebasepro/common": "0.2.3",
19
- "@rebasepro/types": "0.2.3",
20
- "@rebasepro/ui": "0.2.3",
21
- "@rebasepro/utils": "0.2.3",
22
- "@rebasepro/core": "0.2.3"
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",
@@ -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 Authenticator configuration";
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
- authenticator,
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 authenticator
161
+ * Validate access gate
162
162
  */
163
163
  const {
164
164
  authLoading,
165
165
  canAccessMainView,
166
166
  notAllowedError
167
- } = useValidateAuthenticator({
167
+ } = useFirebaseAccessGate({
168
168
  authController,
169
- authenticator,
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: any, loading: boolean }) => {
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 { Authenticator, AnalyticsEvent, AppView, AppViewsBuilder, EntityCollection, EntityCollectionsBuilder, RebasePlugin, Locale, PropertyConfig } from "@rebasepro/types";
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 an Authenticator function to discriminate which users can
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
- authenticator?: boolean | Authenticator<FirebaseUserWrapper>;
73
+ accessGate?: boolean | FirebaseAccessGate<FirebaseUserWrapper>;
73
74
 
74
75
  /**
75
76
  * List of sign in options that will be displayed in the login
@@ -6,3 +6,4 @@ export * from "./useFirestoreDriver";
6
6
  export * from "./useFirebaseRealTimeDBDelegate";
7
7
  export * from "./useRecaptcha";
8
8
  export * from "./useBuildUserManagement";
9
+ export * from "./useFirebaseAccessGate";
@@ -15,7 +15,7 @@ export interface InitializeAppCheckProps {
15
15
  export interface InitializeAppCheckResult {
16
16
  loading: boolean;
17
17
  appCheckVerified?: boolean;
18
- error?: any;
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<any>();
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: any) {
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 = any> = Omit<USER, "roles"> & { roles: string[] };
15
+ type UserWithRoleIds<USER extends User = User> = Omit<USER, "roles"> & { roles: string[] };
16
16
 
17
- export interface UserManagementDelegateParams<CONTROLLER extends AuthController<any> = AuthController<any>> {
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<any> = AuthController<any>,
71
- USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
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: any): void {
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: any): void {
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 authenticator: Authenticator<USER> = useCallback(({ user }) => {
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
- authenticator,
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: any, entityId?: string | number): Promise<boolean> => {
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
- }: any): boolean => {
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: any): any {
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, any> = {};
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: any, database: Database): any {
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
- return ref(database, `${data.slug}/${data.id}`);
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: any) {
136
- if (e?.code === "storage/object-not-found") return null;
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: any) {
174
- if (e?.code === "storage/object-not-found") return {
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
  };