reactfire 4.0.1 → 4.2.1

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/sdk.d.ts CHANGED
@@ -1,15 +1,29 @@
1
1
  import * as React from 'react';
2
+ import type { AppCheck } from 'firebase/app-check';
2
3
  import type { Auth } from 'firebase/auth';
3
4
  import type { Analytics } from 'firebase/analytics';
4
5
  import type { Database } from 'firebase/database';
5
6
  import type { Firestore } from 'firebase/firestore';
7
+ import type { Functions } from 'firebase/functions';
6
8
  import type { FirebasePerformance } from 'firebase/performance';
7
9
  import type { FirebaseStorage } from 'firebase/storage';
8
10
  import type { RemoteConfig } from 'firebase/remote-config';
9
11
  import { FirebaseApp } from 'firebase/app';
10
12
  import { ObservableStatus } from './useObservable';
11
13
  import { ReactFireOptions } from '.';
12
- declare type FirebaseSdks = Auth | Analytics | Database | Firestore | FirebasePerformance | FirebaseStorage | RemoteConfig;
14
+ export declare const AppCheckSdkContext: React.Context<AppCheck | undefined>;
15
+ export declare const AuthSdkContext: React.Context<Auth | undefined>;
16
+ export declare const AnalyticsSdkContext: React.Context<Analytics | undefined>;
17
+ export declare const DatabaseSdkContext: React.Context<Database | undefined>;
18
+ export declare const FirestoreSdkContext: React.Context<Firestore | undefined>;
19
+ export declare const FunctionsSdkContext: React.Context<Functions | undefined>;
20
+ export declare const StorageSdkContext: React.Context<FirebaseStorage | undefined>;
21
+ export declare const PerformanceSdkContext: React.Context<FirebasePerformance | undefined>;
22
+ export declare const RemoteConfigSdkContext: React.Context<RemoteConfig | undefined>;
23
+ declare type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
24
+ export declare const AppCheckProvider: (props: React.PropsWithChildren<{
25
+ sdk: AppCheck;
26
+ }>) => JSX.Element;
13
27
  export declare const AuthProvider: (props: React.PropsWithChildren<{
14
28
  sdk: Auth;
15
29
  }>) => JSX.Element;
@@ -22,6 +36,9 @@ export declare const DatabaseProvider: (props: React.PropsWithChildren<{
22
36
  export declare const FirestoreProvider: (props: React.PropsWithChildren<{
23
37
  sdk: Firestore;
24
38
  }>) => JSX.Element;
39
+ export declare const FunctionsProvider: (props: React.PropsWithChildren<{
40
+ sdk: Functions;
41
+ }>) => JSX.Element;
25
42
  export declare const PerformanceProvider: (props: React.PropsWithChildren<{
26
43
  sdk: FirebasePerformance;
27
44
  }>) => JSX.Element;
@@ -31,18 +48,22 @@ export declare const StorageProvider: (props: React.PropsWithChildren<{
31
48
  export declare const RemoteConfigProvider: (props: React.PropsWithChildren<{
32
49
  sdk: RemoteConfig;
33
50
  }>) => JSX.Element;
51
+ export declare const useAppCheck: () => AppCheck;
34
52
  export declare const useAuth: () => Auth;
35
53
  export declare const useAnalytics: () => Analytics;
36
54
  export declare const useDatabase: () => Database;
37
55
  export declare const useFirestore: () => Firestore;
56
+ export declare const useFunctions: () => Functions;
38
57
  export declare const usePerformance: () => FirebasePerformance;
39
58
  export declare const useStorage: () => FirebaseStorage;
40
59
  export declare const useRemoteConfig: () => RemoteConfig;
41
60
  declare type InitSdkHook<Sdk extends FirebaseSdks> = (initializer: (firebaseApp: FirebaseApp) => Promise<Sdk>, options?: ReactFireOptions<Sdk>) => ObservableStatus<Sdk>;
61
+ export declare const useInitAppCheck: InitSdkHook<AppCheck>;
42
62
  export declare const useInitAuth: InitSdkHook<Auth>;
43
63
  export declare const useInitAnalytics: InitSdkHook<Analytics>;
44
64
  export declare const useInitDatabase: InitSdkHook<Database>;
45
65
  export declare const useInitFirestore: InitSdkHook<Firestore>;
66
+ export declare const useInitFunctions: InitSdkHook<Functions>;
46
67
  export declare const useInitPerformance: InitSdkHook<FirebasePerformance>;
47
68
  export declare const useInitRemoteConfig: InitSdkHook<RemoteConfig>;
48
69
  export declare const useInitStorage: InitSdkHook<FirebaseStorage>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.1",
2
+ "version": "4.2.1",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -14,16 +14,18 @@
14
14
  "start": "tsdx watch",
15
15
  "build": "tsdx build",
16
16
  "test:tsdx": "tsdx test",
17
- "test": "firebase emulators:exec --only firestore,database,auth,storage --project=rxfire-525a3 \"tsdx test\"",
17
+ "test": "firebase emulators:exec --project=rxfire-525a3 \"tsdx test\"",
18
18
  "test:firestore": "firebase emulators:exec --only firestore --project=rxfire-525a3 \"tsdx test firestore --verbose\"",
19
19
  "test:database": "firebase emulators:exec --only database --project=rxfire-525a3 \"tsdx test database --verbose\"",
20
20
  "test:auth": "firebase emulators:exec --only auth --project=rxfire-525a3 \"tsdx test auth --verbose\"",
21
+ "test:functions": "firebase emulators:exec --only functions --project=rxfire-525a3 \"tsdx test functions --verbose\"",
21
22
  "test:storage": "firebase emulators:exec --only storage --project=rxfire-525a3 \"tsdx test storage --verbose\"",
22
23
  "test:useObservable": "tsdx test useObservable --verbose",
23
24
  "lint": "tsdx lint src test",
24
25
  "size": "size-limit",
25
26
  "analyze": "size-limit --why",
26
- "docs": "typedoc --options typedoc.json && markdown-toc -i docs/use.md"
27
+ "docs": "typedoc --options typedoc.json && markdown-toc -i docs/use.md",
28
+ "docs:fork": "typedoc --options typedoc.json --gitRemote upstream && markdown-toc -i docs/use.md"
27
29
  },
28
30
  "peerDependencies": {
29
31
  "firebase": "^9.0.0",
@@ -81,7 +83,7 @@
81
83
  "babel-jest": "^26.6.3",
82
84
  "babel-plugin-minify-replace": "^0.5.0",
83
85
  "eslint-plugin-no-only-tests": "^2.6.0",
84
- "firebase": "^9.0.0",
86
+ "firebase": "^9.0.1",
85
87
  "firebase-tools": "^9.16.0",
86
88
  "globalthis": "^1.0.1",
87
89
  "husky": "^4.3.0",
@@ -99,7 +101,7 @@
99
101
  "typescript": "^4.2.4"
100
102
  },
101
103
  "dependencies": {
102
- "rxfire": "^6.0.0",
104
+ "rxfire": "^6.0.2",
103
105
  "rxjs": "^6.6.3 || ^7.0.1"
104
106
  },
105
107
  "resolutions": {
@@ -48,14 +48,16 @@ export class SuspenseSubject<T> extends Subject<T> {
48
48
  return this._hasValue || !!this._error;
49
49
  }
50
50
 
51
- get value(): T | undefined {
51
+ get value(): T {
52
52
  // TODO figure out how to reset the cache here, if I _reset() here before throwing
53
53
  // it doesn't seem to work.
54
54
  // As it is now, this will burn the cache entry until the timeout fires.
55
55
  if (this._error) {
56
56
  throw this._error;
57
+ } else if (!this.hasValue) {
58
+ throw Error('Can only get value if SuspenseSubject has a value');
57
59
  }
58
- return this._value;
60
+ return this._value as T;
59
61
  }
60
62
 
61
63
  get firstEmission(): Promise<void> {
@@ -0,0 +1,26 @@
1
+ import { httpsCallable as rxHttpsCallable } from 'rxfire/functions';
2
+ import { ReactFireOptions, useObservable, ObservableStatus } from './';
3
+ import { useFunctions } from '.';
4
+
5
+ import type { HttpsCallableOptions } from 'firebase/functions';
6
+
7
+ /**
8
+ * Calls a callable function.
9
+ *
10
+ * @param functionName - The name of the function to call
11
+ * @param options
12
+ */
13
+ export function useCallableFunctionResponse<RequestData, ResponseData>(
14
+ functionName: string,
15
+ options?: ReactFireOptions<ResponseData> & {
16
+ httpsCallableOptions?: HttpsCallableOptions;
17
+ data?: RequestData;
18
+ }
19
+ ): ObservableStatus<ResponseData> {
20
+ const functions = useFunctions();
21
+ const observableId = `functions:callableResponse:${functionName}:${JSON.stringify(options?.data)}:${JSON.stringify(options?.httpsCallableOptions)}`;
22
+ const obsFactory = rxHttpsCallable<RequestData, ResponseData>(functions, functionName, options?.httpsCallableOptions);
23
+ const observable$ = obsFactory(options?.data);
24
+
25
+ return useObservable(observableId, observable$, options);
26
+ }
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export * from './auth';
52
52
  export * from './database';
53
53
  export * from './firebaseApp';
54
54
  export * from './firestore';
55
+ export * from './functions';
55
56
  export * from './performance';
56
57
  export * from './remote-config';
57
58
  export * from './storage';
package/src/sdk.tsx CHANGED
@@ -1,9 +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';
4
5
  import type { Analytics } from 'firebase/analytics';
5
6
  import type { Database } from 'firebase/database';
6
7
  import type { Firestore } from 'firebase/firestore';
8
+ import type { Functions } from 'firebase/functions';
7
9
  import type { FirebasePerformance } from 'firebase/performance';
8
10
  import type { FirebaseStorage } from 'firebase/storage';
9
11
  import type { RemoteConfig } from 'firebase/remote-config';
@@ -13,15 +15,17 @@ import { ObservableStatus, useObservable } from './useObservable';
13
15
  import { from } from 'rxjs';
14
16
  import { ReactFireOptions } from '.';
15
17
 
16
- const AuthSdkContext = React.createContext<Auth | undefined>(undefined);
17
- const AnalyticsSdkContext = React.createContext<Analytics | undefined>(undefined);
18
- const DatabaseSdkContext = React.createContext<Database | undefined>(undefined);
19
- const FirestoreSdkContext = React.createContext<Firestore | undefined>(undefined);
20
- const StorageSdkContext = React.createContext<FirebaseStorage | undefined>(undefined);
21
- const PerformanceSdkContext = React.createContext<FirebasePerformance | undefined>(undefined);
22
- 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);
23
27
 
24
- type FirebaseSdks = Auth | Analytics | Database | Firestore | FirebasePerformance | FirebaseStorage | RemoteConfig;
28
+ type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
25
29
 
26
30
  function getSdkProvider<Sdk extends FirebaseSdks>(SdkContext: React.Context<Sdk | undefined>) {
27
31
  return function SdkProvider(props: React.PropsWithChildren<{ sdk: Sdk }>) {
@@ -65,18 +69,22 @@ function useInitSdk<Sdk extends FirebaseSdks>(
65
69
  return useObservable<Sdk>(`firebase-sdk:${sdkName}:${firebaseApp.name}`, from(initializeSdk), options);
66
70
  }
67
71
 
72
+ export const AppCheckProvider = getSdkProvider<AppCheck>(AppCheckSdkContext);
68
73
  export const AuthProvider = getSdkProvider<Auth>(AuthSdkContext);
69
74
  export const AnalyticsProvider = getSdkProvider<Analytics>(AnalyticsSdkContext);
70
75
  export const DatabaseProvider = getSdkProvider<Database>(DatabaseSdkContext);
71
76
  export const FirestoreProvider = getSdkProvider<Firestore>(FirestoreSdkContext);
77
+ export const FunctionsProvider = getSdkProvider<Functions>(FunctionsSdkContext);
72
78
  export const PerformanceProvider = getSdkProvider<FirebasePerformance>(PerformanceSdkContext);
73
79
  export const StorageProvider = getSdkProvider<FirebaseStorage>(StorageSdkContext);
74
80
  export const RemoteConfigProvider = getSdkProvider<RemoteConfig>(RemoteConfigSdkContext);
75
81
 
82
+ export const useAppCheck = () => useSdk<AppCheck>(AppCheckSdkContext);
76
83
  export const useAuth = () => useSdk<Auth>(AuthSdkContext);
77
84
  export const useAnalytics = () => useSdk<Analytics>(AnalyticsSdkContext);
78
85
  export const useDatabase = () => useSdk<Database>(DatabaseSdkContext);
79
86
  export const useFirestore = () => useSdk<Firestore>(FirestoreSdkContext);
87
+ export const useFunctions = () => useSdk<Functions>(FunctionsSdkContext);
80
88
  export const usePerformance = () => useSdk<FirebasePerformance>(PerformanceSdkContext);
81
89
  export const useStorage = () => useSdk<FirebaseStorage>(StorageSdkContext);
82
90
  export const useRemoteConfig = () => useSdk<RemoteConfig>(RemoteConfigSdkContext);
@@ -86,10 +94,12 @@ type InitSdkHook<Sdk extends FirebaseSdks> = (
86
94
  options?: ReactFireOptions<Sdk>
87
95
  ) => ObservableStatus<Sdk>;
88
96
 
97
+ export const useInitAppCheck: InitSdkHook<AppCheck> = (initializer, options) => useInitSdk<AppCheck>('appcheck', AppCheckSdkContext, initializer, options);
89
98
  export const useInitAuth: InitSdkHook<Auth> = (initializer, options) => useInitSdk<Auth>('auth', AuthSdkContext, initializer, options);
90
99
  export const useInitAnalytics: InitSdkHook<Analytics> = (initializer, options) => useInitSdk<Analytics>('analytics', AnalyticsSdkContext, initializer, options);
91
100
  export const useInitDatabase: InitSdkHook<Database> = (initializer, options) => useInitSdk<Database>('database', DatabaseSdkContext, initializer, options);
92
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);
93
103
  export const useInitPerformance: InitSdkHook<FirebasePerformance> = (initializer, options) =>
94
104
  useInitSdk<FirebasePerformance>('performance', PerformanceSdkContext, initializer, options);
95
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
  }