reactfire 4.0.0-rc.1 → 4.0.1-exp.f6d2351

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.
@@ -1,17 +1,12 @@
1
- import { useRemoteConfig } from '../';
2
- import { useObservable, ObservableStatus } from '../useObservable';
3
- import { getValue, getString, getBoolean, getNumber, getAll, AllParameters } from './getValue';
1
+ import { useRemoteConfig } from './';
2
+ import { useObservable, ObservableStatus } from './useObservable';
3
+ import { getValue, getString, getBoolean, getNumber, getAll, AllParameters } from 'rxfire/remote-config';
4
4
  import { Observable } from 'rxjs';
5
5
 
6
6
  import type { RemoteConfig, Value as RemoteConfigValue } from 'firebase/remote-config';
7
7
 
8
8
  type Getter$<T> = (remoteConfig: RemoteConfig, key: string) => Observable<T>;
9
9
 
10
- interface RemoteConfigWithPrivate extends RemoteConfig {
11
- // This is a private API, assume optional
12
- _storage?: { appName: string };
13
- }
14
-
15
10
  /**
16
11
  * Helper function to construct type safe functions. Since Remote Config has
17
12
  * methods that return different types for values, we need to be extra safe
@@ -23,9 +18,7 @@ interface RemoteConfigWithPrivate extends RemoteConfig {
23
18
  function useRemoteConfigValue_INTERNAL<T>(key: string, getter: Getter$<T>): ObservableStatus<T> {
24
19
  const remoteConfig = useRemoteConfig();
25
20
 
26
- // INVESTIGATE need to use a public API to get at the app name, one doesn't appear to exist...
27
- // we might need to iterate over the Firebase apps and check for remoteConfig equality? this works for now
28
- const appName = (remoteConfig as RemoteConfigWithPrivate)._storage?.appName;
21
+ const appName = remoteConfig.app.name;
29
22
  const $value = getter(remoteConfig, key);
30
23
 
31
24
  const observableId = `remoteConfig:${key}:${getter.name}:${appName}`;
@@ -37,7 +30,6 @@ function useRemoteConfigValue_INTERNAL<T>(key: string, getter: Getter$<T>): Obse
37
30
  * Remote Config Value.
38
31
  *
39
32
  * @param key The parameter key in Remote Config
40
- * @param remoteConfig Optional instance. If not provided ReactFire will either grab the default instance or lazy load.
41
33
  */
42
34
  export function useRemoteConfigValue(key: string): ObservableStatus<RemoteConfigValue> {
43
35
  return useRemoteConfigValue_INTERNAL<RemoteConfigValue>(key, getValue);
@@ -46,7 +38,6 @@ export function useRemoteConfigValue(key: string): ObservableStatus<RemoteConfig
46
38
  /**
47
39
  * Convience method similar to useRemoteConfigValue. Returns a `string` from a Remote Config parameter.
48
40
  * @param key The parameter key in Remote Config
49
- * @param remoteConfig Optional instance. If not provided ReactFire will either grab the default instance or lazy load.
50
41
  */
51
42
  export function useRemoteConfigString(key: string): ObservableStatus<string> {
52
43
  return useRemoteConfigValue_INTERNAL<string>(key, getString);
@@ -55,7 +46,6 @@ export function useRemoteConfigString(key: string): ObservableStatus<string> {
55
46
  /**
56
47
  * Convience method similar to useRemoteConfigValue. Returns a `number` from a Remote Config parameter.
57
48
  * @param key The parameter key in Remote Config
58
- * @param remoteConfig Optional instance. If not provided ReactFire will either grab the default instance or lazy load.
59
49
  */
60
50
  export function useRemoteConfigNumber(key: string): ObservableStatus<number> {
61
51
  return useRemoteConfigValue_INTERNAL<number>(key, getNumber);
@@ -64,7 +54,6 @@ export function useRemoteConfigNumber(key: string): ObservableStatus<number> {
64
54
  /**
65
55
  * Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Remote Config parameter.
66
56
  * @param key The parameter key in Remote Config
67
- * @param remoteConfig Optional instance. If not provided ReactFire will either grab the default instance or lazy load.
68
57
  */
69
58
  export function useRemoteConfigBoolean(key: string): ObservableStatus<boolean> {
70
59
  return useRemoteConfigValue_INTERNAL<boolean>(key, getBoolean);
@@ -73,7 +62,6 @@ export function useRemoteConfigBoolean(key: string): ObservableStatus<boolean> {
73
62
  /**
74
63
  * Convience method similar to useRemoteConfigValue. Returns allRemote Config parameters.
75
64
  * @param key The parameter key in Remote Config
76
- * @param remoteConfig Optional instance. If not provided ReactFire will either grab the default instance or lazy load.
77
65
  */
78
66
  export function useRemoteConfigAll(key: string): ObservableStatus<AllParameters> {
79
67
  return useRemoteConfigValue_INTERNAL<AllParameters>(key, getAll);
package/src/sdk.tsx CHANGED
@@ -1,8 +1,11 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import type { AppCheck } from 'firebase/app-check';
3
4
  import type { Auth } from 'firebase/auth';
5
+ import type { Analytics } from 'firebase/analytics';
4
6
  import type { Database } from 'firebase/database';
5
7
  import type { Firestore } from 'firebase/firestore';
8
+ import type { Functions } from 'firebase/functions';
6
9
  import type { FirebasePerformance } from 'firebase/performance';
7
10
  import type { FirebaseStorage } from 'firebase/storage';
8
11
  import type { RemoteConfig } from 'firebase/remote-config';
@@ -12,38 +15,25 @@ import { ObservableStatus, useObservable } from './useObservable';
12
15
  import { from } from 'rxjs';
13
16
  import { ReactFireOptions } from '.';
14
17
 
15
- const AuthSdkContext = React.createContext<Auth | undefined>(undefined);
16
- const DatabaseSdkContext = React.createContext<Database | undefined>(undefined);
17
- const FirestoreSdkContext = React.createContext<Firestore | undefined>(undefined);
18
- const StorageSdkContext = React.createContext<FirebaseStorage | undefined>(undefined);
19
- const PerformanceSdkContext = React.createContext<FirebasePerformance | undefined>(undefined);
20
- const RemoteConfigSdkContext = React.createContext<RemoteConfig | undefined>(undefined);
18
+ export const AppCheckSdkContext = React.createContext<AppCheck | undefined>(undefined);
19
+ export const AuthSdkContext = React.createContext<Auth | undefined>(undefined);
20
+ export const AnalyticsSdkContext = React.createContext<Analytics | undefined>(undefined);
21
+ export const DatabaseSdkContext = React.createContext<Database | undefined>(undefined);
22
+ export const FirestoreSdkContext = React.createContext<Firestore | undefined>(undefined);
23
+ export const FunctionsSdkContext = React.createContext<Functions | undefined>(undefined);
24
+ export const StorageSdkContext = React.createContext<FirebaseStorage | undefined>(undefined);
25
+ export const PerformanceSdkContext = React.createContext<FirebasePerformance | undefined>(undefined);
26
+ export const RemoteConfigSdkContext = React.createContext<RemoteConfig | undefined>(undefined);
21
27
 
22
- type FirebaseSdks = Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | RemoteConfig;
28
+ type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
23
29
 
24
30
  function getSdkProvider<Sdk extends FirebaseSdks>(SdkContext: React.Context<Sdk | undefined>) {
25
31
  return function SdkProvider(props: React.PropsWithChildren<{ sdk: Sdk }>) {
26
- const contextualAppName = useFirebaseApp().name;
27
- let sdkAppName;
28
-
29
- // @ts-ignore Auth doesn't have field 'app'
30
- if (props.sdk.app) {
31
- // @ts-ignore Auth doesn't have field 'app'
32
- sdkAppName = props.sdk.app.name;
33
-
34
- // @ts-ignore only Auth has field 'name'
35
- } else if (props.sdk.name) {
36
- // @ts-ignore only Auth has field 'name'
37
- sdkAppName = props.sdk.name;
38
- }
32
+ if (!props.sdk) throw new Error('no sdk provided');
39
33
 
40
- if (sdkAppName !== contextualAppName) {
41
- throw new Error('sdk was initialized with a different firebase app');
42
- }
43
-
44
- if (!props.sdk) {
45
- throw new Error('no sdk provided');
46
- }
34
+ const contextualAppName = useFirebaseApp().name;
35
+ const sdkAppName = props?.sdk?.app?.name;
36
+ if (sdkAppName !== contextualAppName) throw new Error('sdk was initialized with a different firebase app');
47
37
 
48
38
  return <SdkContext.Provider value={props.sdk} {...props} />;
49
39
  };
@@ -79,16 +69,22 @@ function useInitSdk<Sdk extends FirebaseSdks>(
79
69
  return useObservable<Sdk>(`firebase-sdk:${sdkName}:${firebaseApp.name}`, from(initializeSdk), options);
80
70
  }
81
71
 
72
+ export const AppCheckProvider = getSdkProvider<AppCheck>(AppCheckSdkContext);
82
73
  export const AuthProvider = getSdkProvider<Auth>(AuthSdkContext);
74
+ export const AnalyticsProvider = getSdkProvider<Analytics>(AnalyticsSdkContext);
83
75
  export const DatabaseProvider = getSdkProvider<Database>(DatabaseSdkContext);
84
76
  export const FirestoreProvider = getSdkProvider<Firestore>(FirestoreSdkContext);
77
+ export const FunctionsProvider = getSdkProvider<Functions>(FunctionsSdkContext);
85
78
  export const PerformanceProvider = getSdkProvider<FirebasePerformance>(PerformanceSdkContext);
86
79
  export const StorageProvider = getSdkProvider<FirebaseStorage>(StorageSdkContext);
87
80
  export const RemoteConfigProvider = getSdkProvider<RemoteConfig>(RemoteConfigSdkContext);
88
81
 
82
+ export const useAppCheck = () => useSdk<AppCheck>(AppCheckSdkContext);
89
83
  export const useAuth = () => useSdk<Auth>(AuthSdkContext);
84
+ export const useAnalytics = () => useSdk<Analytics>(AnalyticsSdkContext);
90
85
  export const useDatabase = () => useSdk<Database>(DatabaseSdkContext);
91
86
  export const useFirestore = () => useSdk<Firestore>(FirestoreSdkContext);
87
+ export const useFunctions = () => useSdk<Functions>(FunctionsSdkContext);
92
88
  export const usePerformance = () => useSdk<FirebasePerformance>(PerformanceSdkContext);
93
89
  export const useStorage = () => useSdk<FirebaseStorage>(StorageSdkContext);
94
90
  export const useRemoteConfig = () => useSdk<RemoteConfig>(RemoteConfigSdkContext);
@@ -98,9 +94,12 @@ type InitSdkHook<Sdk extends FirebaseSdks> = (
98
94
  options?: ReactFireOptions<Sdk>
99
95
  ) => ObservableStatus<Sdk>;
100
96
 
97
+ export const useInitAppCheck: InitSdkHook<AppCheck> = (initializer, options) => useInitSdk<AppCheck>('appcheck', AppCheckSdkContext, initializer, options);
101
98
  export const useInitAuth: InitSdkHook<Auth> = (initializer, options) => useInitSdk<Auth>('auth', AuthSdkContext, initializer, options);
99
+ export const useInitAnalytics: InitSdkHook<Analytics> = (initializer, options) => useInitSdk<Analytics>('analytics', AnalyticsSdkContext, initializer, options);
102
100
  export const useInitDatabase: InitSdkHook<Database> = (initializer, options) => useInitSdk<Database>('database', DatabaseSdkContext, initializer, options);
103
101
  export const useInitFirestore: InitSdkHook<Firestore> = (initializer, options) => useInitSdk<Firestore>('firestore', FirestoreSdkContext, initializer, options);
102
+ export const useInitFunctions: InitSdkHook<Functions> = (initializer, options) => useInitSdk<Functions>('functions', FunctionsSdkContext, initializer, options);
104
103
  export const useInitPerformance: InitSdkHook<FirebasePerformance> = (initializer, options) =>
105
104
  useInitSdk<FirebasePerformance>('performance', PerformanceSdkContext, initializer, options);
106
105
  export const useInitRemoteConfig: InitSdkHook<RemoteConfig> = (initializer, options) =>
package/src/storage.tsx CHANGED
@@ -1,33 +1,11 @@
1
1
  import * as React from 'react';
2
- import { getDownloadURL } from 'rxfire/storage';
3
- import { Observable } from 'rxjs';
2
+ import { getDownloadURL, fromTask } from 'rxfire/storage';
4
3
  import { ReactFireOptions, useObservable, ObservableStatus, useStorage } from './';
5
4
  import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp';
6
5
  import { ref } from 'firebase/storage';
7
6
 
8
7
  import type { UploadTask, UploadTaskSnapshot, StorageReference, FirebaseStorage } from 'firebase/storage';
9
8
 
10
- /**
11
- * modified version of rxFire's _fromTask
12
- *
13
- * @param task
14
- */
15
- function _fromTask(task: UploadTask) {
16
- return new Observable<UploadTaskSnapshot>((subscriber) => {
17
- const progress = (snap: UploadTaskSnapshot) => {
18
- return subscriber.next(snap);
19
- };
20
- const error = (e: any) => subscriber.error(e);
21
- const complete = () => {
22
- return subscriber.complete();
23
- };
24
- task.on('state_changed', progress, error, complete);
25
-
26
- // I REMOVED THE UNSUBSCRIBE RETURN BECAUSE IT CANCELS THE UPLOAD
27
- // https://github.com/firebase/firebase-js-sdk/issues/1659
28
- });
29
- }
30
-
31
9
  /**
32
10
  * Subscribe to the progress of a storage task
33
11
  *
@@ -37,7 +15,7 @@ function _fromTask(task: UploadTask) {
37
15
  */
38
16
  export function useStorageTask<T = unknown>(task: UploadTask, ref: StorageReference, options?: ReactFireOptions<T>): ObservableStatus<UploadTaskSnapshot | T> {
39
17
  const observableId = `storage:task:${ref.toString()}`;
40
- const observable$ = _fromTask(task);
18
+ const observable$ = fromTask(task);
41
19
 
42
20
  return useObservable(observableId, observable$, options);
43
21
  }
@@ -63,55 +63,75 @@ export interface ObservableStatus<T> {
63
63
  firstValuePromise: Promise<void>;
64
64
  }
65
65
 
66
+ function reducerFactory<T>(observable: SuspenseSubject<T>) {
67
+ return function reducer(state: ObservableStatus<T>, action: 'value' | 'error' | 'complete'): ObservableStatus<T> {
68
+ // always make sure these values are in sync with the observable
69
+ const newState = {
70
+ ...state,
71
+ hasEmitted: state.hasEmitted || observable.hasValue,
72
+ error: observable.ourError,
73
+ firstValuePromise: observable.firstEmission,
74
+ };
75
+ if (observable.hasValue) {
76
+ newState.data = observable.value;
77
+ }
78
+
79
+ switch (action) {
80
+ case 'value':
81
+ newState.status = 'success';
82
+ return newState;
83
+ case 'error':
84
+ newState.status = 'error';
85
+ return newState;
86
+ case 'complete':
87
+ newState.isComplete = true;
88
+ return newState;
89
+ default:
90
+ throw new Error(`invalid action "${action}"`);
91
+ }
92
+ };
93
+ }
94
+
66
95
  export function useObservable<T = unknown>(observableId: string, source: Observable<T>, config: ReactFireOptions = {}): ObservableStatus<T> {
96
+ // Register the observable with the cache
67
97
  if (!observableId) {
68
98
  throw new Error('cannot call useObservable without an observableId');
69
99
  }
70
100
  const observable = preloadObservable(source, observableId);
71
101
 
102
+ // Suspend if suspense is enabled and no initial data exists
72
103
  const hasInitialData = config.hasOwnProperty('initialData') || config.hasOwnProperty('startWithValue');
73
-
104
+ const hasData = observable.hasValue || hasInitialData;
74
105
  const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(config.suspense);
75
-
76
- if (suspenseEnabled === true && !observable.hasValue && (!config?.initialData ?? !config?.startWithValue)) {
106
+ if (suspenseEnabled === true && !hasData) {
77
107
  throw observable.firstEmission;
78
108
  }
79
109
 
80
- const [latest, setValue] = React.useState(() => (observable.hasValue ? observable.value : config.initialData ?? config.startWithValue));
81
- const [isComplete, setIsComplete] = React.useState(false);
82
- const [hasError, setHasError] = React.useState(false);
110
+ const initialState: ObservableStatus<T> = {
111
+ status: hasData ? 'success' : 'loading',
112
+ hasEmitted: hasData,
113
+ isComplete: false,
114
+ data: observable.hasValue ? observable.value : config?.initialData ?? config?.startWithValue,
115
+ error: observable.ourError,
116
+ firstValuePromise: observable.firstEmission,
117
+ };
118
+ const [status, dispatch] = React.useReducer<React.Reducer<ObservableStatus<T>, 'value' | 'error' | 'complete'>>(reducerFactory<T>(observable), initialState);
119
+
83
120
  React.useEffect(() => {
84
121
  const subscription = observable.subscribe({
85
- next: (v) => {
86
- setValue(() => v);
122
+ next: () => {
123
+ dispatch('value');
87
124
  },
88
125
  error: (e) => {
89
- setHasError(true);
126
+ dispatch('error');
90
127
  throw e;
91
128
  },
92
129
  complete: () => {
93
- setIsComplete(true);
130
+ dispatch('complete');
94
131
  },
95
132
  });
96
133
  return () => subscription.unsubscribe();
97
134
  }, [observable]);
98
135
 
99
- let status: ObservableStatus<T>['status'];
100
-
101
- if (hasError) {
102
- status = 'error';
103
- } else if (observable.hasValue || hasInitialData) {
104
- status = 'success';
105
- } else {
106
- status = 'loading';
107
- }
108
-
109
- return {
110
- status,
111
- hasEmitted: observable.hasValue || hasInitialData,
112
- isComplete: isComplete,
113
- data: latest,
114
- error: observable.ourError,
115
- firstValuePromise: observable.firstEmission,
116
- };
136
+ return status;
117
137
  }